Fractal Softworks Forum

Please login or register.

Login with username, password and session length
Advanced search  

News:

Starsector 0.97a is out! (02/02/24); New blog post: Simulator Enhancements (03/13/24)

Pages: 1 ... 4 5 [6] 7

Author Topic: Conquest appreciation thread (0.95.1a)  (Read 8140 times)

intrinsic_parity

  • Admiral
  • *****
  • Posts: 3071
    • View Profile
Re: Conquest appreciation thread (0.95.1a)
« Reply #75 on: October 20, 2022, 09:43:41 AM »

so E(a_i) can be calculated based on E(a_{i-1})
I believe this is incorrect. I don't think you can just use the EV of the previous armor value here.

Say a_i = f(a_i-1,d) (where f is a function representing all the necessary calculations). In general E[a_i] =/= f(E[a_i-1], E[d]) unless f is linear (which it is not). Also, f is really a function of a bunch of other armor cells as well, which makes things even more complicated.
Logged

CapnHector

  • Admiral
  • *****
  • Posts: 1056
    • View Profile
Re: Conquest appreciation thread (0.95.1a)
« Reply #76 on: October 20, 2022, 09:57:04 AM »

so E(a_i) can be calculated based on E(a_{i-1})
I believe this is incorrect. I don't think you can just use the EV of the previous armor value here.

Say a_i = f(a_i-1,d) (where f is a function representing all the necessary calculations). In general E[a_i] =/= f(E[a_i-1], E[d]) unless f is linear (which it is not). Also, f is really a function of a bunch of other armor cells as well, which makes things even more complicated.

Specifically let's assume we are above the minimum armor value. We can do the same thing when below it and the edge cases won't contribute much error. Then we know the explicit function f(a_i, d) and it is a_{i-1}-d/(1+1/a_{i-1} (the latter part from damage * damage /(damage+damage/armor). Now assume d is a constant. Then Cov(d,1/(1+1/(a_{i-1}) = 0. Then by the sum property of E(X) E(f(a_i))=E(a_{i-1})-E(d)*E(1/(1+1/a_{i-1})). E(d) is just d as hit strength is constant in Starsector. So I guess the beef is E(f(a_{i-1})) = E(a_{i-1})-d*E(1/(1+1/a_{i-1})) != E(a_i). But why?

Given that this is a sequence and not a continuous function can't we do E(f(f(f(f(...(a_0))...) To get E(a_i)?
« Last Edit: October 20, 2022, 10:04:06 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: Conquest appreciation thread (0.95.1a)
« Reply #77 on: October 20, 2022, 10:27:40 AM »

(the latter part from damage * damage /(damage+damage/armor).

The formula is damage*damage/(damage + armor) which could be written as damage/(1+armor/damage) (I think that is what you are trying write), so sure, if you are not considering armor cells, that is fine. Really, that armor value is actually a summation over several different armor cells, and the damage is distributed over multiple cells (meaning that damage is done to this cell when the shot hits other cells), so when calculating expectation, there are a bunch more terms. But all of that is not too difficult to handle for one shot.

Given that this is a sequence and not a continuous function can't we do E(f(f(f(f(...(a_0))...) To get E(a_i)?
The fundamental issue is that in general E[f(f(...f(a_0)))] =/= E[f(E[...f(a_0)])] for arbitrary f. The rhs is what we can easily calculate, the lhs is the value we are interested in. It's possible the error is not too significant, but it might be worth testing that.
« Last Edit: October 20, 2022, 10:51:08 AM by intrinsic_parity »
Logged

CapnHector

  • Admiral
  • *****
  • Posts: 1056
    • View Profile
Re: Conquest appreciation thread (0.95.1a)
« Reply #78 on: October 20, 2022, 10:51:09 AM »

Yeah I have all the attention span of a drunk goldfish rn. Sorry about all the typos. Math shouldn't be done on a bathroom break on your phone.
 Anyway

Let's fix that.

"let's assume we are above the minimum armor value. We can do the same thing when below it and the edge cases won't contribute much error. Then we know the explicit function f(a_i, d) and it is a_{i-1}-d/(1+a_{i-1}/d) (the latter part from damage * damage /(damage+armor). Now assume d is a constant. Then Cov(d,1/(1+(a_{i-1}/d) = 0. Then by the sum property of E(X) E(f(a_i))=E(a_{i-1})-E(d)*E(1/(1+a_{i-1}/d}). E(d) is just d as hit strength is constant in Starsector. So I guess the beef is E(f(a_{i-1})) = E(a_{i-1})-d*E(1/(1+a_{i-1}/d)) != E(a_i). But why?

I get that this isn't so for an arbitrary function, the rules of calculating EV don't work like that. But why not for this one?
Can't really compute the error when can't define the function other than as a sequence.
« Last Edit: October 20, 2022, 10:56:42 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: Conquest appreciation thread (0.95.1a)
« Reply #79 on: October 20, 2022, 02:42:13 PM »

I'm pretty sure that E[f(f(...f(a_0)))] = E[f(E[...f(a_0)])] is only true for linear f (it's definitely true in that case).

Earlier I was trying to figure out what E[f(...f(a_0))] was exactly, although I think I did a bad job of explaining what I was doing. I think it should be something like E[a_3] = sum_s3(sum_s2(sum_s1(  f(f(f(a_0,s1),s2),s3)  p(s1)p(s2)p(s3) ))) where sn is the location of the nth shot. But after some numerical experimentation, I don't think it matter enough to worry about it. The error was relatively small, and didn't blow up in cases where it took a lot of shots to kill, so I think it should be fine.

@Vanshilar
Also, I implemented a simulation of armor cells so I could test things for myself (and matched your results with the uniform distribution). But your 3rd case (the least spread out one) is still not a very tight distribution. From my testing, it seems approximately equivalent to a uniform distribution over ~9-11 cells in terms of the number of shots required to kill. You could have a much tighter spread than that quite reasonably. In your case with 500 HE damage, a uniform spread over ~5 cells gets down to about 38 shots in my testing. And all shots hitting one cell takes 35 hits to kill. Also, with those numbers, a simple sim (with no armor cells) takes 35 shots to kill as well.

If I pick another scenario, say 1000 armor, 10000 hull, 100 energy damage (pulse laser vs eagle). I get:
uniform spread over 15 cells: ~431 shots
uniform spread over 9 cells: ~341 shots
uniform spread over 5 cells: ~282 shots
all shots hitting one cell: 221 shots
simple sim, no cells : 203 shots

In that case, the effect of distributing the damage is much larger, almost a 2x increase in shots to kill for damage spread over the entire ship (not even missing), compared to perfectly accurate fire.
Logged

CapnHector

  • Admiral
  • *****
  • Posts: 1056
    • View Profile
Re: Conquest appreciation thread (0.95.1a)
« Reply #80 on: October 20, 2022, 09:54:10 PM »

Alrighty well since we can't agree on a distribution function a priori let's just cop out and construct the distribution at this point.

We'll give R the parameters for range, weapon firing arc, ship width in pixels, and no. of armor cells. Start with Dominator so 15 armor cells.

The spray arc of the weapon is 2pi*range*((max angle)/360 #in Starsector weapon max angle is in degrees). Then the weapon hits the target target width / arc of the time. Additionally this gives us a chance to implement Thaago's fudge factor, which is an error in the hit accuracy due to enemy movement etc. The distribution of the error really shouldn't matter much but let's be realistic and draw it from a normal distribution anyway. mu(error)=0 and the standard deviation is the "fudge factor".

So, in R,
R


#dominator, hullhp, shieldregen, shieldmax, startingarmor, widthinpixels, armorcells
ship <- c(14000, 500, 10000, 1500, 180, 15)

#engagementrange
range <- 1000

#weaponaccuracy - this will be made a function of time and weapon later. the accuracy of a hellbore is 10
acc <- 10

#fudge factor
errorsd <- 0.01
#the fudge factor should be a function of range (more error in position at greater range), but not a function of weapon firing angle, and be expressed in terms of pixels
error <- errorsd*range

#where does one shot hit within the weapon's arc of fire, in pixel difference from the target? get a random angle in degrees according to a uniform distribution,
#then consider that the circumference is 2 pi * range pixels, so the hit coordinates in pixels are
shotangle <- function(acc) return(runif(1,-acc/2,acc/2)/360*2*pi*range)

#how much is the visual arc of the ship in rad?
shipangle <- ship[5]/(2* pi *range)

#how much is the visual arc of a single cell of armor in rad?
cellangle <- shipangle/ship[6]

#now assume the weapon is targeting the center of the ship's visual arc and that the ship is in the center of the weapon's firing arc
#which cell will the shot hit, or will it miss?
#call the cells (MISS, cell1, cell2, ... ,celli, MISS) and get a vector giving the (maximum for negative / minimum for positive) angles for hitting each
anglerangevector <- vector(mode="double", length = ship[6]+1)
anglerangevector[1] <- -shipangle/2
for (i in 1:(length(anglerangevector)-1)) anglerangevector[i+1] <- anglerangevector+cellangle

#now convert it to pixels
anglerangevector <- anglerangevector*2*pi*range

#this vector will store the hits
shipcellvector <- vector(mode="double", length = ship[6]+2)

#now add a random positional error to the coordinates of the hit
hitlocation <- function(acc){
  location <- shotangle(acc)
  location <- location + rnorm(1,0,error)
  return(location)
}

#so which box was hit?
cellhit <- function(angle){
  if(angle < anglerangevector[1]) return(1)
  if(angle > anglerangevector[ship[6]+1]) return(ship[6]+2)
  for (i in 1:length(anglerangevector)) {
    if ((angle > anglerangevector) & (angle <= anglerangevector[i+1])) return(i+1)
  }
}


#now let's run this over 10 000 shots and count how many shots hit where
for (i in 1:10000) {
  wherehit <- cellhit(hitlocation(acc))
  shipcellvector[wherehit] <- shipcellvector[wherehit] + 1
}

shipcellvector
[close]

Here are some distributions. First and last cell represent misses, rest are armor cells.
Hellbore cannon, no fudge factor
Spoiler
> shipcellvector
 [1]   0 503 696 663 700 646 704 720 663 661 724 692 669 681 744 534   0
[close]
(it's expected that it shouldn't miss since the weapon firing arc is smaller than the visual arc of the ship)

Hellbore cannon, fudge factor 0.01 (random positional error SD = 10 px)
Spoiler
> shipcellvector
 [1] 152 438 593 711 744 700 687 738 641 717 663 655 660 683 621 441 156
[close]

Heavy autocannon, no fudge factor
Spoiler
> shipcellvector
 [1] 2154  389  413  376  390  381  400  339  371  423  368  392  398  334  382  383 2107
[close]

Heavy autocannon, fudge factor 0.05 (random positional error SD = 50 px)
Spoiler
> shipcellvector
 [1] 2254  363  358  384  360  379  374  362  393  363  380  392  384  401  355  324 2174
[close]

Now the plan is to use this code to generate shot distributions as a function of time using e.g. 100 000 samples, then summing damage matrices according to distributions to get the expected damage. We'll ignore the possible inaccuracy in cell by cell expected values if it exists because I have no idea how to eliminate it if it does and it shouldn't be large.

This post was edited to make the positional error a function of range instead of shot angle which would obviously have been inaccurate to reality.

Add: "average distribution" for an unknown weapon (average over max spread angle from 1 to 20, the maximum for normal weapons), as % of shots hitting cell, no fudge factor
Spoiler

for (j in 1:20){
  acc <- j
for (i in 1:10000) {
  wherehit <- cellhit(hitlocation(acc))
  shipcellvector[wherehit] <- shipcellvector[wherehit] + 1
}
}

shipcellvector <- shipcellvector/sum(shipcellvector)
shipcellvector <- round(shipcellvector*100,2)
shipcellvector

> shipcellvector
 [1]  7.78  2.58  3.15  3.73  4.52  5.63  6.83  9.65 12.35  9.56  6.86  5.51  4.52  3.73  3.16  2.58  7.86
[close]

Summary: the distribution of hits is uniform if the maximum spread of the weapon is precisely known with no fudge factor applied (assuming the game samples the firing angle from one uniform distribution), but with a special problem of edge cases due to the discrete nature of the armor cells. If the maximum spread of the weapon is not known then a normal distribution should be used instead. The error should be normal, resulting in a final distribution of hits that is X+Y where X is a uniform distribution but with the edge problem and Y is a normal distribution. Given how laborious it is to calculate and justify this distribution I'll instead just simulate it for each ship and weapon instead and use the pre-calculated distribution for the expected location of hits.
« Last Edit: October 21, 2022, 12:25:31 AM by CapnHector »
Logged
5 ships vs 5 Ordos: Executor · Invictus · Paragon · Astral · Legion · Onslaught · Odyssey | Video LibraryHiruma Kai's Challenge

BCS

  • Captain
  • ****
  • Posts: 279
    • View Profile
Re: Conquest appreciation thread (0.95.1a)
« Reply #81 on: October 20, 2022, 10:45:29 PM »

You now at this point you might as well ask Alex to give you the code...
Logged

CapnHector

  • Admiral
  • *****
  • Posts: 1056
    • View Profile
Re: Conquest appreciation thread (0.95.1a)
« Reply #82 on: October 21, 2022, 01:56:30 AM »

You now at this point you might as well ask Alex to give you the code...

I'll take that as a compliment. Also when this simulation of the Conquest is done and we know what the optimum Conquest is we should go back to appreciating the Conquest as the focus of this thread.

Anyway, next step. Let's try to simulate the Hephaestus. We know the maximum spread / shot is 10. And spread/shot starts at 0 and is increased by 2 for each shot fired. In this model you never stop firing so spread decay is not a concern.

R

# this function generates the shot distribution per 1 shot with 100000 samples
createdistribution <- function(acc){
  distributionvector <- vector(mode="double", length = ship[6]+2)
  for (i in 1:100000){
    wherehit <- cellhit(hitlocation(acc))
    distributionvector[wherehit] <- distributionvector[wherehit] +1
  }
  return(distributionvector/sum(distributionvector))
}

# this is the default distribution of damage to armor cells
b <- matrix(0,nrow=5,ncol=5)
b[1:5,2:4] <- 1/30
b[2:4,1:5] <- 1/30
b[2:4,2:4] <- 1/15
b[1,1] <- 0
b[1,5] <- 0
b[5,1] <- 0
b[5,5] <- 0

#this function generates a sum of matrices multiplied by the distribution

createhitmatrix <- function(acc){
  hitmatrix <- matrix(0,5,ship[6]+4)
  distributionvector <- createdistribution(acc)
  for (i in 1:ship[6]){
    hitmatrix[,i:(i+4)] <- hitmatrix[,i:(i+4)]+b*(distributionvector[i+1])
  }
  return(hitmatrix)
}

#for weapons with damage changing over time we need a sequence of matrices
createhitmatrixsequence <- function(accvector){
  hitmatrixsequence <- list()
  for (i in 1:length(accvector)){
    hitmatrixsequence[] <- createhitmatrix(accvector)
  }
  return(hitmatrixsequence)
}
   
capture.output(createhitmatrixsequence(c(2,4,6,8)), file = "matrixsequence.txt")

[close]

Now here's what we get, sorry about the line breaks but R has a hard time printing lists of large matrices
Spoiler

# (fudge factor = 0)

[[1]]
     [,1] [,2] [,3] [,4] [,5] [,6]       [,7]       [,8]       [,9]      [,10]      [,11]      [,12]
[1,]    0    0    0    0    0    0 0.00000000 0.01093667 0.02235400 0.03333333 0.02239667 0.01097933
[2,]    0    0    0    0    0    0 0.01093667 0.03329067 0.05568733 0.06666667 0.05573000 0.03337600
[3,]    0    0    0    0    0    0 0.01093667 0.03329067 0.05568733 0.06666667 0.05573000 0.03337600
[4,]    0    0    0    0    0    0 0.01093667 0.03329067 0.05568733 0.06666667 0.05573000 0.03337600
[5,]    0    0    0    0    0    0 0.00000000 0.01093667 0.02235400 0.03333333 0.02239667 0.01097933
          [,13] [,14] [,15] [,16] [,17] [,18] [,19]
[1,] 0.00000000     0     0     0     0     0     0
[2,] 0.01097933     0     0     0     0     0     0
[3,] 0.01097933     0     0     0     0     0     0
[4,] 0.01097933     0     0     0     0     0     0
[5,] 0.00000000     0     0     0     0     0     0

[[2]]
     [,1] [,2] [,3] [,4]        [,5]        [,6]        [,7]       [,8]       [,9]      [,10]      [,11]
[1,]    0    0    0    0 0.000000000 0.002363333 0.008168333 0.01383833 0.01714167 0.01709767 0.01716367
[2,]    0    0    0    0 0.002363333 0.010531667 0.022006667 0.03334333 0.04240767 0.04573633 0.04232867
[3,]    0    0    0    0 0.002363333 0.010531667 0.022006667 0.03334333 0.04240767 0.04573633 0.04232867
[4,]    0    0    0    0 0.002363333 0.010531667 0.022006667 0.03334333 0.04240767 0.04573633 0.04232867
[5,]    0    0    0    0 0.000000000 0.002363333 0.008168333 0.01383833 0.01714167 0.01709767 0.01716367
          [,12]       [,13]       [,14]       [,15] [,16] [,17] [,18] [,19]
[1,] 0.01382833 0.008067333 0.002331333 0.000000000     0     0     0     0
[2,] 0.03332333 0.021895667 0.010398667 0.002331333     0     0     0     0
[3,] 0.03332333 0.021895667 0.010398667 0.002331333     0     0     0     0
[4,] 0.03332333 0.021895667 0.010398667 0.002331333     0     0     0     0
[5,] 0.01382833 0.008067333 0.002331333 0.000000000     0     0     0     0

[[3]]
     [,1] [,2] [,3]        [,4]        [,5]        [,6]       [,7]       [,8]       [,9]      [,10]
[1,]    0    0    0 0.000000000 0.003317667 0.007085667 0.01090300 0.01136800 0.01138367 0.01141667
[2,]    0    0    0 0.003317667 0.010403333 0.017988667 0.02558867 0.02983733 0.03038567 0.03053467
[3,]    0    0    0 0.003317667 0.010403333 0.017988667 0.02558867 0.02983733 0.03038567 0.03053467
[4,]    0    0    0 0.003317667 0.010403333 0.017988667 0.02558867 0.02983733 0.03038567 0.03053467
[5,]    0    0    0 0.000000000 0.003317667 0.007085667 0.01090300 0.01136800 0.01138367 0.01141667
          [,11]      [,12]      [,13]       [,14]       [,15]       [,16] [,17] [,18] [,19]
[1,] 0.01151800 0.01155433 0.01101367 0.007129667 0.003309667 0.000000000     0     0     0
[2,] 0.03063867 0.03020200 0.02587767 0.018143333 0.010439333 0.003309667     0     0     0
[3,] 0.03063867 0.03020200 0.02587767 0.018143333 0.010439333 0.003309667     0     0     0
[4,] 0.03063867 0.03020200 0.02587767 0.018143333 0.010439333 0.003309667     0     0     0
[5,] 0.01151800 0.01155433 0.01101367 0.007129667 0.003309667 0.000000000     0     0     0

[[4]]
     [,1]         [,2]         [,3]        [,4]        [,5]       [,6]       [,7]        [,8]        [,9]
[1,]    0 0.0000000000 0.0009296667 0.003798667 0.006680333 0.00863500 0.00865600 0.008685667 0.008666333
[2,]    0 0.0009296667 0.0047283333 0.010479000 0.016245000 0.02108967 0.02309233 0.023118000 0.023056333
[3,]    0 0.0009296667 0.0047283333 0.010479000 0.016245000 0.02108967 0.02309233 0.023118000 0.023056333
[4,]    0 0.0009296667 0.0047283333 0.010479000 0.016245000 0.02108967 0.02309233 0.023118000 0.023056333
[5,]    0 0.0000000000 0.0009296667 0.003798667 0.006680333 0.00863500 0.00865600 0.008685667 0.008666333
           [,10]      [,11]      [,12]    [,13]    [,14]       [,15]      [,16]        [,17]        [,18]
[1,] 0.008615667 0.00853300 0.00850200 0.008485 0.008530 0.006600333 0.00377800 0.0009043333 0.0000000000
[2,] 0.022950000 0.02281133 0.02269133 0.022683 0.020793 0.016034667 0.01037833 0.0046823333 0.0009043333
[3,] 0.022950000 0.02281133 0.02269133 0.022683 0.020793 0.016034667 0.01037833 0.0046823333 0.0009043333
[4,] 0.022950000 0.02281133 0.02269133 0.022683 0.020793 0.016034667 0.01037833 0.0046823333 0.0009043333
[5,] 0.008615667 0.00853300 0.00850200 0.008485 0.008530 0.006600333 0.00377800 0.0009043333 0.0000000000
     [,19]
[1,]     0
[2,]     0
[3,]     0
[4,]     0
[5,]     0

[close]

You can see the Hephaestus' damage becoming more spread out over multiple shots.
« Last Edit: October 21, 2022, 02:03:01 AM by CapnHector »
Logged
5 ships vs 5 Ordos: Executor · Invictus · Paragon · Astral · Legion · Onslaught · Odyssey | Video LibraryHiruma Kai's Challenge

Vanshilar

  • Admiral
  • *****
  • Posts: 585
    • View Profile
Re: Conquest appreciation thread (0.95.1a)
« Reply #83 on: October 21, 2022, 04:28:12 AM »

@Vanshilar
500 HE damage vs 1500 armor is going to result in armor cells getting stripped supper quickly which is not really an interesting case (that's closer to torpedo damage than any gun other than hellbore). I think it would be more useful to consider a much lower hit strength, like 100/200 energy or something vs 1000 armor (that's like medium energy vs cruiser armor which seems like a pretty middle of the road case). Pretty sure that will have a large impact.

No, it's the opposite. The smaller the hits, the more closely CapnHector's probability wave method approximates hits being fired one at a time, since each hit only takes off a bit of armor at a time. It's the large hits where the errors would be more apparent since hits will take off large chunks in specific sections while leaving other sections intact, while the probability wave takes off armor across the whole width relatively evenly.

For example, for 100 energy shots against 1000 armor, with uniform probability on 15 hittable armor cells, then for 500 hits, CapnHector's probability wave method predicts 5679 armor taken off and 14496 damage to hull. If the hits were done one at a time, after 500 hits, for 200 samples, the average was 5655 +- 57 to armor and 14397 +- 175 to hull. So now it's less than 1% off instead of before when it was over 3% off.

So basically, this method of approximating hits is pretty good, with it only being a couple percent off if big weapons are being used. For smaller weapons, the error is negligible.

Also, it makes no sense to me that in case 1 25%+ more shots miss than case 3, but somehow it only takes 3 more shots to kill. Unless you mean that 46 hits are required to kill, not counting misses.

I was assuming every shot hit, i.e. the normal distribution was normalized such that the part that fit within the 15 hittable cells summed to 1. Me talking about the part that misses was just to give an idea of how wide or narrow that distribution was. Maybe I should have said 46 "hits" instead of 46 "shots" or something, but that's what I meant. I guess I'll use that going forward to remove ambiguity.

Also, I implemented a simulation of armor cells so I could test things for myself (and matched your results with the uniform distribution). But your 3rd case (the least spread out one) is still not a very tight distribution. From my testing, it seems approximately equivalent to a uniform distribution over ~9-11 cells in terms of the number of shots required to kill. You could have a much tighter spread than that quite reasonably.

It really depends on what you're fighting, i.e. your weapon's projectile speed and spread, the target's size and maneuverability, etc. For that distribution, 77% of the hits were landing within the center 7 cells and 95% of the hits were landing within the center 11 cells. So you basically hit almost all the time. If you're fighting something like an Onslaught at close range (i.e. essentially non-maneuvering target with big armor cells) then it may be a good representation. If you're trying to chase down a frigate, then probably not. For example, earlier I posted about killing a sim Dominator, which usually took off around 8-9k armor. At base 1500 armor, meaning each column of cells was 500 armor, this translates to stripping off a band of armor that's around 16-18 cells wide. So it was hitting a band of armor around 12-14 wide (basically the entire front face), with the rest missing (and yes there were a number of misses). And Dominators are not exactly known for being maneuverable.

A uniform distribution basically means that you're hitting across the entire width of the band of armor, and then end up breaking through all that armor into hull at pretty much the same time. A normal distribution basically means that you open up a hole in the middle of the armor first, start doing damage to hull at that point, and then gradually widen that hole in the armor as you go along. It's up to you the player to decide what type of armor-busting you see more frequently.

In that case, the effect of distributing the damage is much larger, almost a 2x increase in shots to kill for damage spread over the entire ship (not even missing), compared to perfectly accurate fire.

Well yes, the wider the band of armor being assumed, the longer it's going to take to break through that armor. I mean that's what most of the discussion is about. Simulating hits to shields is easy, simulating hits to hull is easy, it's simulating hits to armor that's difficult. In this case the wider the band of armor being assumed, the more armor there is to churn through and thus the longer it takes. But also, the higher the hit strength, and/or the weapon damage type (HE being best, frag being worst), the faster the armor gets broken through and the less it matters.

Assuming uniform distribution, the number of hits to break through armor is *almost* exactly linear with the width of armor (number of cells wide) being assumed. (The difference is that once the armor band is wide enough, the weapon won't necessarily finish off the armor at the very left and very right before the ship dies.) So for example, for a 1000 armor, 10k hull ship (ignoring shields), a 100 energy damage weapon takes 221 hits to kill it if always hitting the same spot, and 428 hits to kill it if it's uniformly spread across 15 hittable armor cells (width of 19 cells total), a 94% increase. It takes 150 hits to hull to kill it, so this means that the hits to armor increased from 71 to 278.

If the weapon were 100 HE damage, however, it takes 150 hits if hitting the same spot, and 224 hits if spread across 15 hittable armor cells, a 49% increase. With 125 hits needed to kill hull, this means that the hits to armor increased from 25 to 99.

So in this case, assuming a band of 15 hittable armor cells basically quadruples the number of hits needed to break through armor compared with assuming that all hits land in the same spot. And the relationship between number of cells wide and number of hits to armor needed is basically linear.

If we now go back to 1500 armor, 14k hull ship (ignoring shields), a 500 energy damage weapon takes 44 hits to kill it if hitting the same spot, 74 hits if across 15 hittable armor cells, a 68% increase. Number of hits to armor increases from 12 to 42. If that's now a 500 HE damage weapon, then it's 35 hits to kill if hitting the same spot, 46 hits to kill if across 15 hittable armor cells, a 31% increase. Number of hits to armor increases from 5 to 16. So the number of hits to armor needed triples if you assume 15 hittable armor cells instead of 1.

The practical takeaway from this is that if you have a relatively weak weapon (low weapon hit strength relative to target's armor), you'll want to make sure the shots are accurate to not have to go through as much armor. If you have a relatively strong weapon, then you don't have to worry as much about hitting the same spot because the effect won't be as big.
Logged

CapnHector

  • Admiral
  • *****
  • Posts: 1056
    • View Profile
Re: Conquest appreciation thread (0.95.1a)
« Reply #84 on: October 21, 2022, 07:41:21 AM »

Next step (working on this piecewise between some things)

We give every weapon data on how its accuracy degrades
Spoiler

#WEAPON ACCURACY
#missiles do not have spread
squallacc <- c(0)
locustacc <- c(0)
hurricaneacc <- c(0)
harpoonacc <- c(0)
sabotacc <- c(0)

#gauss has a spread of 0 and no increase per shot
gaussacc <- c(0)
#hephaestus has a spread of 0 and it increases by 2 per shot to a max of 10
hephaestusacc <- c(seq(0,10,2))
#mark ix has a spread of 0 and it increases by 2 per shot to a max of 15
markixacc <- c(seq(0,15,2),15)
#mjolnir has a spread of 0 and it increases by 1 per shot to a max of 5
mjolniracc <- c(seq(1,5,1))
#hellbore has a spread of 10
hellboreacc <- c(10)
#storm needler has a spread of 10
stormneedleracc <- c(10)
[close]

Then we calculate a hit chance based on the distribution for each weapon and the damage matrix for each weapon and how it changes over time, and bind this to the list entry for each weapon

Spoiler

if(weapon1[4] != ""){
  hitchancevector <- vector(mode = "double", length = length(weapon1[[5]]))
  for (i in 1:length(weapon1[[5]])){
    hitchancevector <- hitchance(weapon1[[5]])
  }
  hitchancevector
  weapon1[[6]] <- hitchancevector
  weapon1[[7]] <- createhitmatrixsequence(weapon1[[5]])
}
[close]

change the damage functions a little
Spoiler

damageattimepoint <- function(weapon, timepoint, armor, startingarmor, hitx, hity, shots){
    # vectors in R are indexed starting from 1
  hitmatrix <- weapon[[7]][[min(shots,length(weapon[[7]]))+1]]
  nohits <- weapon[[3]][(timepoint %% (length(weapon[[3]])))+1]
  if (nohits == 0) {return(0)} else {
    damagesum <- 0
    for (i in 1:nohits) {
#      damagesum <- damagesum + as.double(weapon[[1]]*hitmatrix[hitx,hity])
     damagesum <- damagesum + armordamageselectivereduction(as.double(weapon[[1]]*hitmatrix[hitx,hity]),armor,startingarmor)
     shots <- shots + 1
     hitmatrix <- weapon[[7]][[min(shots,length(weapon[[7]])+1)]]
    }
    return(damagesum)
  }
}

# reduce shield hp loss per hit by hit chance. Still use the full damage to calculate whether to block!

    if (shieldhp > shielddamageattimepoint(weapon1, timepoint)*weapon1mult){
      shieldhp <- shieldhp - shielddamageattimepoint(weapon1, timepoint)*weapon1mult*weapon1[[6]][weapon1shots]
      shieldhp <- max(shieldhp, 0)

[...]
      for (j in 2:9){
        for (i in 2:4){
          hulldamage <- hulldamage+max(0,weapon1mult*damageattimepoint(weapon1, timepoint,armormatrix[i,j],startingarmor,i,j,weapon1shots)-armormatrix[i,j])/weapon1mult
          armormatrix[i,j] <- max(0,armormatrix[i,j]-weapon1mult*damageattimepoint(weapon1, timepoint,armormatrix[i,j],startingarmor,i,j,weapon1shots)*weapon1[[6]][min(weapon1shots,length(weapon1[[6]])+1)])
        }}
etc.

[close]

Putting it all together we have this
ConquestTimeSeries.R
library(ggplot2)
library(ggthemes)

#SHIP
#dominator, hullhp, shieldregen, shieldmax, startingarmor, widthinpixels, armorcells
ship <- c(14000, 500, 10000, 1500, 180, 15)

#engagementrange
range <- 1000

#weaponaccuracy - this will be made a function of time and weapon later. the accuracy of a hellbore is 10
acc <- 10

#fudge factor
errorsd <- 0.00
#the fudge factor should be a function of range (more error in position at greater range), but not a function of weapon firing angle, and be expressed in terms of pixels
error <- errorsd*range

#where does one shot hit within the weapon's arc of fire, in pixel difference from the target? get a random angle in degrees according to a uniform distribution,
#then consider that the circumference is 2 pi * range pixels, so the hit coordinates in pixels are
shotangle <- function(acc) return(runif(1,-acc/2,acc/2)/360*2*pi*range)

#how much is the visual arc of the ship in rad?
shipangle <- ship[5]/(2* pi *range)

#how much is the visual arc of a single cell of armor in rad?
cellangle <- shipangle/ship[6]

#now assume the weapon is targeting the center of the ship's visual arc and that the ship is in the center of the weapon's firing arc
#which cell will the shot hit, or will it miss?
#call the cells (MISS, cell1, cell2, ... ,celli, MISS) and get a vector giving the (maximum for negative / minimum for positive) angles for hitting each
anglerangevector <- vector(mode="double", length = ship[6]+1)
anglerangevector[1] <- -shipangle/2
for (i in 1:(length(anglerangevector)-1)) anglerangevector[i+1] <- anglerangevector+cellangle

#now convert it to pixels
anglerangevector <- anglerangevector*2*pi*range

#this vector will store the hits
shipcellvector <- vector(mode="double", length = ship[6]+2)

#now add a random positional error to the coordinates of the hit
hitlocation <- function(acc){
  location <- shotangle(acc)
  location <- location + rnorm(1,0,error)
  return(location)
}

#so which box was hit?
cellhit <- function(angle){
  if(angle < anglerangevector[1]) return(1)
  if(angle > anglerangevector[ship[6]+1]) return(ship[6]+2)
  for (i in 1:length(anglerangevector)) {
    if ((angle > anglerangevector) & (angle <= anglerangevector[i+1])) return(i+1)
  }
}


# this function generates the shot distribution per 1 shot with 100000 samples
createdistribution <- function(acc){
  distributionvector <- vector(mode="double", length = ship[6]+2)
  for (i in 1:100000){
    wherehit <- cellhit(hitlocation(acc))
    distributionvector[wherehit] <- distributionvector[wherehit] +1
  }
  return(distributionvector/sum(distributionvector))
}

# this is the default distribution of damage to armor cells
b <- matrix(0,nrow=5,ncol=5)
b[1:5,2:4] <- 1/30
b[2:4,1:5] <- 1/30
b[2:4,2:4] <- 1/15
b[1,1] <- 0
b[1,5] <- 0
b[5,1] <- 0
b[5,5] <- 0

#this function generates a sum of matrices multiplied by the distribution

createhitmatrix <- function(acc){
  hitmatrix <- matrix(0,5,ship[6]+4)
  distributionvector <- createdistribution(acc)
  for (i in 1:ship[6]){
    hitmatrix[,i:(i+4)] <- hitmatrix[,i:(i+4)]+b*(distributionvector[i+1])
  }
  return(hitmatrix)
}


hitchance <- function(acc){
  hitchance <- 0
  distributionvector <- createdistribution(acc)
  hitchance <- (1-(distributionvector[1]+distributionvector[ship[6]]))
  return(hitchance)
}

#for weapons with damage changing over time we need a sequence of matrices
createhitmatrixsequence <- function(accvector){
  hitmatrixsequence <- list()
  for (i in 1:length(accvector)){
    hitmatrixsequence[] <- createhitmatrix(accvector)
  }
  return(hitmatrixsequence)
}
 
armordamage <- function(damage, armor, startingarmor) damage*(max(0.15,damage/(damage+max(0.05*startingarmor,armor))))
armordamageselectivereduction <- function(damage, armor,startingarmor) {
  useminarmor <- 0
  if(armor < 0.05*startingarmor / 15) useminarmor <- 1
  if(useminarmor == 0){
    if(armor == 0) {return (damage)}
    return(damage*(max(0.15,damage/(damage+armor))))
  }
  else{
    return(damage*(max(0.15,damage/(damage+0.05*startingarmor/15))))
  }
}

#squall fires 2 missiles / sec for 10 secs, then recharges for 10 secs
squalltics <- c(2,2,2,2,2,2,2,2,2,2,0,0,0,0,0,0,0,0,0,0)
#locust fires 10 missiles / sec for 4 secs, then recharges for 5 secs
locusttics <- c(10,10,10,10,0,0,0,0,0)
#hurricane fires 9 missiles every 15 seconds
hurricanetics <- c(9,0,0,0,0,0,0,0,0,0,0,0,0,0,0)
#harpoon pod fires 4 missiles in 1 second, then recharges for 8 seconds (in reality 8.25)
harpoontics <- c(4,0,0,0,0,0,0,0,0)
#sabot pod fires 2*5 missiles, then recharges for 8 seconds (in reality 8.75 seconds and firing time is .5 seconds)
sabottics <- c(10,0,0,0,0,0,0,0,0)
#gauss fires 1 shot, then charges for 1 second
gausstics <- c(1,0)
#hephaestus fires 4 shots / sec
hephaestustics <- c(4)
#mark ix fires 4 shots every 3 seconds
markixtics <- c(4,0,0)
#mjolnir fires 4 shots per 3 seconds. Since there is no neat way to handle this when time is discrete at 1 sec we'll handle as follows
mjolnirtics <- c(1,1,2,1,2,1,2,1,1)
#hellbore fires 1 shot per 4 seconds
hellboretics <- c(1,0,0,0)
#storm needler fires 10 shots per second
stormneedlertics <- c(10)

#WEAPON ACCURACY
#missiles do not have spread
squallacc <- c(0)
locustacc <- c(0)
hurricaneacc <- c(0)
harpoonacc <- c(0)
sabotacc <- c(0)

#gauss has a spread of 0 and no increase per shot
gaussacc <- c(0)
#hephaestus has a spread of 0 and it increases by 2 per shot to a max of 10
hephaestusacc <- c(seq(0,10,2))
#mark ix has a spread of 0 and it increases by 2 per shot to a max of 15
markixacc <- c(seq(0,15,2),15)
#mjolnir has a spread of 0 and it increases by 1 per shot to a max of 5
mjolniracc <- c(seq(1,5,1))
#hellbore has a spread of 10
hellboreacc <- c(10)
#storm needler has a spread of 10
stormneedleracc <- c(10)

#damage per shot, damage type (2=kinetic, 0.5=he, 0.25=frag, 1=energy), tics, weapon name, weapon accuracy over time, hit chance
squall <- list(250, 2, squalltics, "Squall", squallacc)
locust <- list(200, 0.25, locusttics, "Locust", locustacc)
hurricane <- list(500, 0.5, hurricanetics, "Hurricane", hurricaneacc)
harpoon <- list(750, 0.5, harpoontics, "Harpoon", harpoonacc)
sabot <- list(200, 2, sabottics, "Sabot", sabotacc)
gauss <- list(700, 2, gausstics, "Gauss", gaussacc)
hephaestus <- list(120, 0.5, hephaestustics, "Hephaestus", hephaestusacc)
markix <- list(200, 2, markixtics, "Mark IX", markixacc)
mjolnir <- list(400, 1, mjolnirtics, "Mjolnir", mjolniracc)
hellbore <- list(750, 0.5, hellboretics, "Hellbore", hellboreacc)
stormneedler <- list(50, 2, stormneedlertics, "Storm Needler", stormneedleracc)
dummy <- list(0,0,c(0),"",c(0),c(0))

weapon1 <- squall
weapon2 <- squall
weapon3 <- harpoon
weapon4 <- harpoon
weapon5 <- hephaestus
weapon6 <- gauss

#now create the sequences of hit matrices and hit chances for each weapon

if(weapon1[4] != ""){
  hitchancevector <- vector(mode = "double", length = length(weapon1[[5]]))
  for (i in 1:length(weapon1[[5]])){
    hitchancevector <- hitchance(weapon1[[5]])
  }
  hitchancevector
  weapon1[[6]] <- hitchancevector
  weapon1[[7]] <- createhitmatrixsequence(weapon1[[5]])
}

if(weapon2[4] != ""){
  hitchancevector <- vector(mode = "double", length = length(weapon2[[5]]))
  for (i in 1:length(weapon2[[5]])){
    hitchancevector <- hitchance(weapon2[[5]])
  }
  hitchancevector
  weapon2[[6]] <- hitchancevector
  weapon2[[7]] <- createhitmatrixsequence(weapon2[[5]])
}

if(weapon3[4] != ""){
  hitchancevector <- vector(mode = "double", length = length(weapon3[[5]]))
  for (i in 1:length(weapon3[[5]])){
    hitchancevector <- hitchance(weapon3[[5]])
  }
  hitchancevector
  weapon3[[6]] <- hitchancevector
  weapon3[[7]] <- createhitmatrixsequence(weapon3[[5]])
}

if(weapon4[4] != ""){
  hitchancevector <- vector(mode = "double", length = length(weapon4[[5]]))
  for (i in 1:length(weapon4[[5]])){
    hitchancevector <- hitchance(weapon4[[5]])
  }
  hitchancevector
  weapon4[[6]] <- hitchancevector
  weapon4[[7]] <- createhitmatrixsequence(weapon4[[5]])
}

if(weapon5[4] != ""){
  hitchancevector <- vector(mode = "double", length = length(weapon5[[5]]))
  for (i in 1:length(weapon5[[5]])){
    hitchancevector <- hitchance(weapon5[[5]])
  }
  hitchancevector
  weapon5[[6]] <- hitchancevector
  weapon5[[7]] <- createhitmatrixsequence(weapon5[[5]])
}

if(weapon6[4] != ""){
  hitchancevector <- vector(mode = "double", length = length(weapon6[[5]]))
  for (i in 1:length(weapon6[[5]])){
    hitchancevector <- hitchance(weapon6[[5]])
  }
  hitchancevector
  weapon6[[6]] <- hitchancevector
  weapon6[[7]] <- createhitmatrixsequence(weapon6[[5]])
}

shieldblock <- 0

shielddamageattimepoint <- function(weapon, timepoint){
  nohits <- weapon[[3]][(timepoint %% (length(weapon[[3]])))+1]
  if (nohits == 0) {return(0)} else {
    return(weapon[[1]]*nohits)
  }
}

damageattimepoint <- function(weapon, timepoint, armor, startingarmor, hitx, hity, shots){
    # vectors in R are indexed starting from 1
  hitmatrix <- weapon[[7]][[min(shots,length(weapon[[7]]))]]
  nohits <- weapon[[3]][(timepoint %% (length(weapon[[3]])))+1]
  if (nohits == 0) {return(0)} else {
    damagesum <- 0
    for (i in 1:nohits) {
#      damagesum <- damagesum + as.double(weapon[[1]]*hitmatrix[hitx,hity])
     damagesum <- damagesum + armordamageselectivereduction(as.double(weapon[[1]]*hitmatrix[hitx,hity]),armor,startingarmor)
     shots <- shots + 1
     hitmatrix <- weapon[[7]][[min(shots,length(weapon[[7]]))]]
    }
    return(damagesum)
  }
}


weapon1shots <- 1
weapon2shots <- 1
weapon3shots <- 1
weapon4shots <- 1
weapon5shots <- 1
weapon6shots <- 1

armormatrix <- matrix(ship[4]/15,5,ship[6]+4)

timeseries <- function(timepoint, shieldhp, armorhp, hullhp, shieldregen, shieldmax, startingarmor,armormatrix){
  weaponacc <- 0
  #are we using shield to block?
  shieldblock <- 0
  hulldamage <- 0
  if(hullhp > 0){} else {shieldhp <- 0}
 
  #1. shields. if shieldhp is sufficient, use shield to block
  if (weapon1[[4]] !=""){
    weapon1mult <- unlist(weapon1[2])
    if (shieldhp > shielddamageattimepoint(weapon1, timepoint)*weapon1mult){
      shieldhp <- shieldhp - shielddamageattimepoint(weapon1, timepoint)*weapon1mult*weapon1[[6]][min(weapon1shots,length(weapon1[[6]]))]
      shieldhp <- max(shieldhp, 0)
      if(shielddamageattimepoint(weapon1,timepoint) > 0) {shieldblock <- 1}
    } else {
      #if you did not use shield to block, regenerate flux
      #2. armor and hull
      if(unlist(weapon1[2])==0.25){weapon1mult = 0.25} else {weapon1mult= 1 / unlist(weapon1[2])}
      #2.1. damage armor and hull
      hulldamage <- 0   
     
      #damageattimepoint <- function(weapon, timepoint, armor, startingarmor, hitx, hity, shots){

      for (j in 2:9){
        for (i in 2:4){
          hulldamage <- hulldamage+max(0,weapon1mult*damageattimepoint(weapon1, timepoint,armormatrix[i,j],startingarmor,i,j,weapon1shots)-armormatrix[i,j])/weapon1mult
          armormatrix[i,j] <- max(0,armormatrix[i,j]-weapon1mult*damageattimepoint(weapon1, timepoint,armormatrix[i,j],startingarmor,i,j,weapon1shots)*weapon1[[6]][min(weapon1shots,length(weapon1[[6]]))])
        }}
     
     
      for (j in 2:9){
        for (i in c(1,5)){
          hulldamage <- hulldamage+max(0,weapon1mult*damageattimepoint(weapon1, timepoint,armormatrix[i,j],startingarmor,i,j,weapon1shots)-armormatrix[i,j])/weapon1mult
          armormatrix[i,j] <- max(0,armormatrix[i,j]-weapon1mult*damageattimepoint(weapon1, timepoint,armormatrix[i,j],startingarmor,i,j,weapon1shots)*weapon1[[6]][min(weapon1shots,length(weapon1[[6]]))])
        }}
      for (j in c(1,10)){
        for (i in 2:4){
          hulldamage <- hulldamage+max(0,weapon1mult*damageattimepoint(weapon1, timepoint,armormatrix[i,j],startingarmor,i,j,weapon1shots)-armormatrix[i,j])/weapon1mult
          armormatrix[i,j] <- max(0,armormatrix[i,j]-weapon1mult*damageattimepoint(weapon1, timepoint,armormatrix[i,j],startingarmor,i,j,weapon1shots)*weapon1[[6]][min(weapon1shots,length(weapon1[[6]]))])
        }}
     
      hullhp <- hullhp - hulldamage*weapon1[[6]][min(weapon1shots,length(weapon1[[6]]))]
      hullhp <- max(hullhp, 0)
     
      armorhp <- sum(armormatrix)/(46/15)
      if(hullhp==0) armorhp <- 0
     
    }
  }
  weapon1shots <- weapon1shots + weapon1[[3]][(timepoint %% (length(weapon1[[3]])+1))]

 
  #repeat for other weapons
  if (weapon2[[4]] !=""){
    weapon2mult <- unlist(weapon2[2])
    if (shieldhp > shielddamageattimepoint(weapon2, timepoint)*weapon2mult){
      shieldhp <- shieldhp - shielddamageattimepoint(weapon2, timepoint)*weapon2mult*weapon2[[6]][min(weapon2shots,length(weapon2[[6]]))]
      shieldhp <- max(shieldhp, 0)
      if(shielddamageattimepoint(weapon2,timepoint) > 0) {shieldblock <- 1}
    } else {
      #if you did not use shield to block, regenerate flux
      #2. armor and hull
      if(unlist(weapon2[2])==0.25){weapon2mult = 0.25} else {weapon2mult= 1 / unlist(weapon2[2])}
      #2.1. damage armor and hull
      hulldamage <- 0   
     
      #damageattimepoint <- function(weapon, timepoint, armor, startingarmor, hitx, hity, shots){
     
      for (j in 2:9){
        for (i in 2:4){
          hulldamage <- hulldamage+max(0,weapon2mult*damageattimepoint(weapon2, timepoint,armormatrix[i,j],startingarmor,i,j,weapon2shots)-armormatrix[i,j])/weapon2mult
          armormatrix[i,j] <- max(0,armormatrix[i,j]-weapon2mult*damageattimepoint(weapon2, timepoint,armormatrix[i,j],startingarmor,i,j,weapon2shots)*weapon2[[6]][min(weapon2shots,length(weapon2[[6]]))])
        }}
     
     
      for (j in 2:9){
        for (i in c(1,5)){
          hulldamage <- hulldamage+max(0,weapon2mult*damageattimepoint(weapon2, timepoint,armormatrix[i,j],startingarmor,i,j,weapon2shots)-armormatrix[i,j])/weapon2mult
          armormatrix[i,j] <- max(0,armormatrix[i,j]-weapon2mult*damageattimepoint(weapon2, timepoint,armormatrix[i,j],startingarmor,i,j,weapon2shots)*weapon2[[6]][min(weapon2shots,length(weapon2[[6]]))])
        }}
      for (j in c(1,10)){
        for (i in 2:4){
          hulldamage <- hulldamage+max(0,weapon2mult*damageattimepoint(weapon2, timepoint,armormatrix[i,j],startingarmor,i,j,weapon2shots)-armormatrix[i,j])/weapon2mult
          armormatrix[i,j] <- max(0,armormatrix[i,j]-weapon2mult*damageattimepoint(weapon2, timepoint,armormatrix[i,j],startingarmor,i,j,weapon2shots)*weapon2[[6]][min(weapon2shots,length(weapon2[[6]]))])
        }}
     
      hullhp <- hullhp - hulldamage*weapon2[[6]][min(weapon2shots,length(weapon2[[6]]))]
      hullhp <- max(hullhp, 0)
     
      armorhp <- sum(armormatrix)/(46/15)
      if(hullhp==0) armorhp <- 0
     
    }
  }
  weapon2shots <- weapon2shots + weapon2[[3]][(timepoint %% (length(weapon2[[3]])))+1]
  if (weapon3[[4]] !=""){
    weapon3mult <- unlist(weapon3[2])
    if (shieldhp > shielddamageattimepoint(weapon3, timepoint)*weapon3mult){
      shieldhp <- shieldhp - shielddamageattimepoint(weapon3, timepoint)*weapon3mult*weapon3[[6]][min(weapon3shots,length(weapon3[[6]]))]
      shieldhp <- max(shieldhp, 0)
      if(shielddamageattimepoint(weapon3,timepoint) > 0) {shieldblock <- 1}
    } else {
      #if you did not use shield to block, regenerate flux
      #2. armor and hull
      if(unlist(weapon3[2])==0.25){weapon3mult = 0.25} else {weapon3mult= 1 / unlist(weapon3[2])}
      #2.1. damage armor and hull
      hulldamage <- 0   
     
      #damageattimepoint <- function(weapon, timepoint, armor, startingarmor, hitx, hity, shots){
     
      for (j in 2:9){
        for (i in 2:4){
          hulldamage <- hulldamage+max(0,weapon3mult*damageattimepoint(weapon3, timepoint,armormatrix[i,j],startingarmor,i,j,weapon3shots)-armormatrix[i,j])/weapon3mult
          armormatrix[i,j] <- max(0,armormatrix[i,j]-weapon3mult*damageattimepoint(weapon3, timepoint,armormatrix[i,j],startingarmor,i,j,weapon3shots)*weapon3[[6]][min(weapon3shots,length(weapon3[[6]]))])
        }}
     
     
      for (j in 2:9){
        for (i in c(1,5)){
          hulldamage <- hulldamage+max(0,weapon3mult*damageattimepoint(weapon3, timepoint,armormatrix[i,j],startingarmor,i,j,weapon3shots)-armormatrix[i,j])/weapon3mult
          armormatrix[i,j] <- max(0,armormatrix[i,j]-weapon3mult*damageattimepoint(weapon3, timepoint,armormatrix[i,j],startingarmor,i,j,weapon3shots)*weapon3[[6]][min(weapon3shots,length(weapon3[[6]]))])
        }}
      for (j in c(1,10)){
        for (i in 2:4){
          hulldamage <- hulldamage+max(0,weapon3mult*damageattimepoint(weapon3, timepoint,armormatrix[i,j],startingarmor,i,j,weapon3shots)-armormatrix[i,j])/weapon3mult
          armormatrix[i,j] <- max(0,armormatrix[i,j]-weapon3mult*damageattimepoint(weapon3, timepoint,armormatrix[i,j],startingarmor,i,j,weapon3shots)*weapon3[[6]][min(weapon3shots,length(weapon3[[6]]))])
        }}
     
      hullhp <- hullhp - hulldamage*weapon3[[6]][min(weapon3shots,length(weapon3[[6]]))]
      hullhp <- max(hullhp, 0)
     
      armorhp <- sum(armormatrix)/(46/15)
      if(hullhp==0) armorhp <- 0
     
    }
  }
  weapon3shots <- weapon3shots + weapon3[[3]][(timepoint %% (length(weapon3[[3]])))+1]
  if (weapon4[[4]] !=""){
    weapon4mult <- unlist(weapon4[2])
    if (shieldhp > shielddamageattimepoint(weapon4, timepoint)*weapon4mult){
      shieldhp <- shieldhp - shielddamageattimepoint(weapon4, timepoint)*weapon4mult*weapon4[[6]][min(weapon4shots,length(weapon4[[6]]))]
      shieldhp <- max(shieldhp, 0)
      if(shielddamageattimepoint(weapon4,timepoint) > 0) {shieldblock <- 1}
    } else {
      #if you did not use shield to block, regenerate flux
      #2. armor and hull
      if(unlist(weapon4[2])==0.25){weapon4mult = 0.25} else {weapon4mult= 1 / unlist(weapon4[2])}
      #2.1. damage armor and hull
      hulldamage <- 0   
     
      #damageattimepoint <- function(weapon, timepoint, armor, startingarmor, hitx, hity, shots){
     
      for (j in 2:9){
        for (i in 2:4){
          hulldamage <- hulldamage+max(0,weapon4mult*damageattimepoint(weapon4, timepoint,armormatrix[i,j],startingarmor,i,j,weapon4shots)-armormatrix[i,j])/weapon4mult
          armormatrix[i,j] <- max(0,armormatrix[i,j]-weapon4mult*damageattimepoint(weapon4, timepoint,armormatrix[i,j],startingarmor,i,j,weapon4shots)*weapon4[[6]][min(weapon4shots,length(weapon4[[6]]))])
        }}
     
     
      for (j in 2:9){
        for (i in c(1,5)){
          hulldamage <- hulldamage+max(0,weapon4mult*damageattimepoint(weapon4, timepoint,armormatrix[i,j],startingarmor,i,j,weapon4shots)-armormatrix[i,j])/weapon4mult
          armormatrix[i,j] <- max(0,armormatrix[i,j]-weapon4mult*damageattimepoint(weapon4, timepoint,armormatrix[i,j],startingarmor,i,j,weapon4shots)*weapon4[[6]][min(weapon4shots,length(weapon4[[6]]))])
        }}
      for (j in c(1,10)){
        for (i in 2:4){
          hulldamage <- hulldamage+max(0,weapon4mult*damageattimepoint(weapon4, timepoint,armormatrix[i,j],startingarmor,i,j,weapon4shots)-armormatrix[i,j])/weapon4mult
          armormatrix[i,j] <- max(0,armormatrix[i,j]-weapon4mult*damageattimepoint(weapon4, timepoint,armormatrix[i,j],startingarmor,i,j,weapon4shots)*weapon4[[6]][min(weapon4shots,length(weapon4[[6]]))])
        }}
     
      hullhp <- hullhp - hulldamage*weapon4[[6]][min(weapon4shots,length(weapon4[[6]]))]
      hullhp <- max(hullhp, 0)
     
      armorhp <- sum(armormatrix)/(46/15)
      if(hullhp==0) armorhp <- 0
     
    }
  }
  weapon4shots <- weapon4shots + weapon4[[3]][(timepoint %% (length(weapon4[[3]])))+1]
  if (weapon5[[4]] !=""){
    weapon5mult <- unlist(weapon5[2])
    if (shieldhp > shielddamageattimepoint(weapon5, timepoint)*weapon5mult){
      shieldhp <- shieldhp - shielddamageattimepoint(weapon5, timepoint)*weapon5mult*weapon5[[6]][min(weapon5shots,length(weapon5[[6]]))]
      shieldhp <- max(shieldhp, 0)
      if(shielddamageattimepoint(weapon5,timepoint) > 0) {shieldblock <- 1}
    } else {
      #if you did not use shield to block, regenerate flux
      #2. armor and hull
      if(unlist(weapon5[2])==0.25){weapon5mult = 0.25} else {weapon5mult= 1 / unlist(weapon5[2])}
      #2.1. damage armor and hull
      hulldamage <- 0   
     
      #damageattimepoint <- function(weapon, timepoint, armor, startingarmor, hitx, hity, shots){
     
      for (j in 2:9){
        for (i in 2:4){
          hulldamage <- hulldamage+max(0,weapon5mult*damageattimepoint(weapon5, timepoint,armormatrix[i,j],startingarmor,i,j,weapon5shots)-armormatrix[i,j])/weapon5mult
          armormatrix[i,j] <- max(0,armormatrix[i,j]-weapon5mult*damageattimepoint(weapon5, timepoint,armormatrix[i,j],startingarmor,i,j,weapon5shots)*weapon5[[6]][min(weapon5shots,length(weapon5[[6]]))])
        }}
     
     
      for (j in 2:9){
        for (i in c(1,5)){
          hulldamage <- hulldamage+max(0,weapon5mult*damageattimepoint(weapon5, timepoint,armormatrix[i,j],startingarmor,i,j,weapon5shots)-armormatrix[i,j])/weapon5mult
          armormatrix[i,j] <- max(0,armormatrix[i,j]-weapon5mult*damageattimepoint(weapon5, timepoint,armormatrix[i,j],startingarmor,i,j,weapon5shots)*weapon5[[6]][min(weapon5shots,length(weapon5[[6]]))])
        }}
      for (j in c(1,10)){
        for (i in 2:4){
          hulldamage <- hulldamage+max(0,weapon5mult*damageattimepoint(weapon5, timepoint,armormatrix[i,j],startingarmor,i,j,weapon5shots)-armormatrix[i,j])/weapon5mult
          armormatrix[i,j] <- max(0,armormatrix[i,j]-weapon5mult*damageattimepoint(weapon5, timepoint,armormatrix[i,j],startingarmor,i,j,weapon5shots)*weapon5[[6]][min(weapon5shots,length(weapon5[[6]]))])
        }}
     
      hullhp <- hullhp - hulldamage*weapon5[[6]][min(weapon5shots,length(weapon5[[6]]))]
      hullhp <- max(hullhp, 0)
     
      armorhp <- sum(armormatrix)/(46/15)
      if(hullhp==0) armorhp <- 0
     
    }
  }
  weapon5shots <- weapon5shots + weapon5[[3]][(timepoint %% (length(weapon5[[3]])))+1]
  if (weapon6[[4]] !=""){
    weapon6mult <- unlist(weapon6[2])
    if (shieldhp > shielddamageattimepoint(weapon6, timepoint)*weapon6mult){
      shieldhp <- shieldhp - shielddamageattimepoint(weapon6, timepoint)*weapon6mult*weapon6[[6]][min(weapon6shots,length(weapon6[[6]]))]
      shieldhp <- max(shieldhp, 0)
      if(shielddamageattimepoint(weapon6,timepoint) > 0) {shieldblock <- 1}
    } else {
      #if you did not use shield to block, regenerate flux
      #2. armor and hull
      if(unlist(weapon6[2])==0.25){weapon6mult = 0.25} else {weapon6mult= 1 / unlist(weapon6[2])}
      #2.1. damage armor and hull
      hulldamage <- 0   
     
      #damageattimepoint <- function(weapon, timepoint, armor, startingarmor, hitx, hity, shots){
     
      for (j in 2:9){
        for (i in 2:4){
          hulldamage <- hulldamage+max(0,weapon6mult*damageattimepoint(weapon6, timepoint,armormatrix[i,j],startingarmor,i,j,weapon6shots)-armormatrix[i,j])/weapon6mult
          armormatrix[i,j] <- max(0,armormatrix[i,j]-weapon6mult*damageattimepoint(weapon6, timepoint,armormatrix[i,j],startingarmor,i,j,weapon6shots)*weapon6[[6]][min(weapon6shots,length(weapon6[[6]]))])
        }}
     
     
      for (j in 2:9){
        for (i in c(1,5)){
          hulldamage <- hulldamage+max(0,weapon6mult*damageattimepoint(weapon6, timepoint,armormatrix[i,j],startingarmor,i,j,weapon6shots)-armormatrix[i,j])/weapon6mult
          armormatrix[i,j] <- max(0,armormatrix[i,j]-weapon6mult*damageattimepoint(weapon6, timepoint,armormatrix[i,j],startingarmor,i,j,weapon6shots)*weapon6[[6]][min(weapon6shots,length(weapon6[[6]]))])
        }}
      for (j in c(1,10)){
        for (i in 2:4){
          hulldamage <- hulldamage+max(0,weapon6mult*damageattimepoint(weapon6, timepoint,armormatrix[i,j],startingarmor,i,j,weapon6shots)-armormatrix[i,j])/weapon6mult
          armormatrix[i,j] <- max(0,armormatrix[i,j]-weapon6mult*damageattimepoint(weapon6, timepoint,armormatrix[i,j],startingarmor,i,j,weapon6shots)*weapon6[[6]][min(weapon6shots,length(weapon6[[6]]))])
        }}
     
      hullhp <- hullhp - hulldamage*weapon6[[6]][min(weapon6shots,length(weapon6[[6]]))]
      hullhp <- max(hullhp, 0)
     
      armorhp <- sum(armormatrix)/(46/15)
      if(hullhp==0) armorhp <- 0
     
    }
  }
  weapon6shots <- weapon6shots + weapon6[[3]][(timepoint %% (length(weapon6[[3]])))+1]
 
   
      hullhp <- hullhp - hulldamage
      hullhp <- max(hullhp, 0)
     
      armorhp <- sum(armormatrix)/(46/15)
      if(hullhp==0) armorhp <- 0
     
   
 
 
 
 
 
  if (shieldblock==0){shieldhp <- min(shieldmax,shieldhp+shieldregen)}
  return(list(timepoint, shieldhp, armorhp, hullhp, shieldregen, shieldmax, startingarmor,armormatrix))
}

totaltime = 60



armorhp <- ship[4]
shieldhp <- ship[3]
hullhp <- ship[1]
shieldregen <- ship[2]
shieldmax <- ship[3]
armorhp <- ship[4]
startingarmor <- ship[4]
armormatrix <- matrix(ship[4]/15,nrow=5,ncol=10)
armormatrix[1,1] <-0
armormatrix[1,10] <-0
armormatrix[5,1] <-0
armormatrix[5,10] <- 0



timeseriesarray <- data.frame(matrix(ncol = 4,nrow=0))

for (t in 1:totaltime){
  state <- timeseries(t,shieldhp,armorhp,hullhp,shieldregen,shieldmax,startingarmor,armormatrix)
  shieldhp <- state[[2]]
  armorhp <- state[[3]]
  hullhp <- state[[4]]
  flux <- shieldmax - shieldhp
  armormatrix <- state[[8]]
  if(hullhp == 0){flux <- 0}
  timeseriesarray <- rbind(timeseriesarray , c(state[[1]], flux/shieldmax*100, state[[3]]/startingarmor*100, state[[4]]/ship[1]*100))
 
}

colnames(timeseriesarray) <-  c("Time", "Flux", "Armor", "Hull")
weaponstitle <- paste(unlist(weapon1[4]),unlist(weapon2[4]),unlist(weapon3[4]),unlist(weapon4[4]),unlist(weapon5[4]),unlist(weapon6[4]))


ggplot(timeseriesarray, aes(x=Time))  +
  geom_line(aes(y = Flux, color = "Flux")) +
  geom_line(aes(y = Armor, color="Armor")) +
  geom_line(aes(y = Hull, color="Hull")) +
  scale_colour_manual("",
                      breaks = c("Flux", "Armor", "Hull"),
                      values = c("lightsteelblue", "red", "maroon")) +
  ylab("% max") +
  xlab("Time (s)") +
  labs(title=weaponstitle)

[close]

Yielding this

(fudge factor of 0)

Now interestingly we are within the time predicted by Vanshilar's simulations now that accuracy has been taken into account (and minimum armor rule is fixed and applied correctly), even without a fudge factor. Next let's try to figure out what the fudge factor should be with Locust-Locust-Harpoon-Harpoon-Gauss-HAC and Vanshilar's data.
« Last Edit: October 21, 2022, 11:48:29 AM by CapnHector »
Logged
5 ships vs 5 Ordos: Executor · Invictus · Paragon · Astral · Legion · Onslaught · Odyssey | Video LibraryHiruma Kai's Challenge

BCS

  • Captain
  • ****
  • Posts: 279
    • View Profile
Re: Conquest appreciation thread (0.95.1a)
« Reply #85 on: December 15, 2022, 05:57:30 AM »

Let me dig out this thread to present Optimizing the Conquest: A Non-mathematical Model of Space Combat.

After the Radiants beat up my ass for the 69th time I finally swallowed up my OCD and stopped trying to make a symmetrical Conquest. Instead I... still tried to make it symmetrical, only with split damage types this time(all kinetic left, all explosive/fragmentation right) Since I knew I was going to need all the kinetic damage I can get, and with only one Squall, I went all out and even put a Resonator on it to boost the kinetic damage output(if you put Sabots on the left, which I tried, the AI is never going to use them - I think it's a range issue, 1200 is just not enough)



The results were... acceptable? This is actually the setup I beat the bounty Omega Ordo with, albeit it took a long time and I suffered losses. Because of this I don't consider the build fully viable, I also don't like to rely on Omega weapons since in current version of the game they are random and limited. I had the Resonators I needed this time, I might not have them the next time. Which is a shame because I really like the idea of a two-faced Conquest. I also like the Hurricane, it looks cool and slaps hard.

That's when I swallowed even more of my OCD and went for double Squalls.



This is probably how most sensible people would fit a Conquest, with the exception of the Maulers and EO. I really wanted to have some explosive damage in there - Squalls are getting nerfed against armor next patch and I do fight enemies other than the Remnant, up to and including Derelict bounties, which really require some explosive weapons if you want to do them in a reasonable amount of time. I used this build for a while and considered it finished but the more I played with it the more flaws I noticed. First of all, noticeable range mismatch between Gauss and Maulers. Second of all, Gauss have very slow rotation speed and AI likes to "wiggle" the ship which causes rather embarassing misses(Later I dumped some flux capacity for Advanced Turret Gyros for this reason) Maulers themselves are kind of meh as well - they fire in a burst which makes it too easy for an enemy ship to simply raise the shield to stop the shots, and AI is not great with slow firing weapons in general since it fires at first thing that's in range. And the damage... I swear that a single shot from a Gauss does more hull damage than entire three shot salvo from a Mauler sometimes.

This is when I remembered my old Conquest with double Mjolnirs and thought... why not? Could it even be done while remaining flux neutral?



It could.

No more problems with turret turn speed, no more problems with range mismatch, no more Maulers, Mjolnir can still crack open armor. Mjolnirs can also swat fighters rather effectively unlike the Gauss, which is great because AI will often fire at enemy fighters when there's nothing else in range. Also note the triple EMP damage(Ion Beam/HVD/Mjolnir)

Is the tank fine without any flux capacitors? Yes! This took me way too long to realize but since Conquest already has a massive base flux capacity, adding capacitors with OP is actually very inefficient. For example this version has base flux capacity of 26k, which means that spending 5 OP on capacitors(+1000 capacity) would increase it by only 1/26th, or by less than 4%. You really want to go all out with vents on the Conquest.

Could it be improved? I guess HVDs could be swapped for Heavy Autocannons, but it's debatable whether or not they are actually better than HVDs, and in my experience they tend to spray a lot given the distances involved, even with Gunnery Implants. And yes, the Efficiency Overhaul is still there.

After playing with Conquests for so long they seem honestly kind of ridiculous. For 40 DP you get two large missile mounts, two medium missile mounts, two large ballistic mounts, two medium ballistic mounts and an Ion Beam - there isn't a single(practical) ship in the game with more firepower per DP spent. And all that on a hull that actually moves, unlike most other capitals. I can't even imagine using other ships now since they just seem so puny in comparison. Maybe the upcoming Pegasus will be better, but I expect it to be at least 50 DP so we'll see about that.
Logged

CapnHector

  • Admiral
  • *****
  • Posts: 1056
    • View Profile
Re: Conquest appreciation thread (0.95.1a)
« Reply #86 on: December 15, 2022, 08:11:44 AM »

I like you already! Thanks for contributing to Conquest appreciation especially as the other thread has turned a little math-y for the moment until we work out some things to do with the model.

Your build looks excellent and Mjolnir Mjolnir was the combo of large ballistics with the fastest times to kill in testing. Here is the build I've personally settled on



Honestly, this ship delivers on so many fronts I really can't be bothered to change the design or consider other capitals anymore. It is burn 10 and can take on almost anything with the game, due to its range, overwhelming firepower and tendency to form natural lines of battle when paired with Steady officers. Although you'll note in this playthrough I paired it with aggressive Brawler LP's, which works quite well (also burn 10 and they can capture objectives kill the frigates that distract your Conquests). This is the build I used to do the Super Redacted mission and also farm Ordos.

I don't think HAC is the way to go though, because the accuracy is very underwhelming. I tried it for a while, but went back. I should probably play around with Mjolnirs since I don't have that much experience with them, despite simulations suggesting better efficiency I wanted to pair Gauss with the missiles because of the range.
Logged
5 ships vs 5 Ordos: Executor · Invictus · Paragon · Astral · Legion · Onslaught · Odyssey | Video LibraryHiruma Kai's Challenge

CapnHector

  • Admiral
  • *****
  • Posts: 1056
    • View Profile
Re: Conquest appreciation thread (0.95.1a)
« Reply #87 on: December 23, 2022, 04:07:48 AM »

I'm going to follow up on this and say that based on some more experience fighting remnants using Mjolnir Mjolnir, I think I have a new favorite. The concerns about range turn out to have been unfounded, since the Remnant ships will close the distance and overwhelm you. I decided to go for what the model said was best, and use Mjolnir Mjolnir Needler Needler, alongside stabilized and hardened shields and of course this build still can reach burn 10 which I like while doing it all. That said, to optimize it for combat, S-mod in the Hardened Shields to gain extra 25 OP.



I was just able to take out a double Ordo of 4 Radiants and 10 Brilliants with these 5 Conquests and a supporting crew of 2 afflictors and 4 brawler LPs with absolutely minimal player input - no piloting of my own, just orders to capture command points and then eliminate orders on the Radiants for the Conquests. Only 4 frigates were lost. I'm not 100% about the Locusts, but I do think they are useful finishers after all and they are predicted to be good by the model. I think they may be useful in that you want to sweep the lesser Remnant ships aside fast so as to not die to the Radiants.

Here are some very nice scenes from a different fight that illustrate Conquest dynamics at their best, Hegemony decided to send an invasion fleet full of capital ships vs. my base of Sindria. Of course, they all died with no losses or need for player input on my side.


Logged
5 ships vs 5 Ordos: Executor · Invictus · Paragon · Astral · Legion · Onslaught · Odyssey | Video LibraryHiruma Kai's Challenge

Grievous69

  • Admiral
  • *****
  • Posts: 2980
    • View Profile
Re: Conquest appreciation thread (0.95.1a)
« Reply #88 on: December 23, 2022, 04:28:06 AM »

That could be even better if you replace the s-modded ADF with something else. Conquest has 8 base burn, not sure what you need 10 burn for against Remnants.
Logged
Please don't take me too seriously.

BCS

  • Captain
  • ****
  • Posts: 279
    • View Profile
Re: Conquest appreciation thread (0.95.1a)
« Reply #89 on: December 23, 2022, 09:56:27 AM »

Base 10 burn fleet is the dream after all
Logged
Pages: 1 ... 4 5 [6] 7