Starfinder: Soldier with Magnetar Rifle#

Advanced weapons are available to all martial characters through the Weapon Proficiency general feat. However, with Weapon Proficiency, when you hit level 5 your proficiency with martial weapons increases to expert, but not that with advanced weapons. This is unlike ancestral Weapon Familiarity feats.

The Magnetar Rifle is an advanced weapon (d12 Analog, Automatic, range 60ft, magazine 30, reload 1). The closest match with martial weapons are the substantially worse Machine Gun (d8 Analog, Automatic, range 40ft, magazine 20, reload 2) or Rotolaser (d8 Automatic, Tech, range 30ft, magazine 10~100 depending on level, reload 1)

Crucially, Area Fire and Auto-Fire use one’s class proficiency, not the weapon proficiency - which raises the question of how do the damage profiles for these weapons compare. So a Soldier using a Magnetar Rifle would use their lowered weapon proficiency for Primary Target and simple Strikes, and their full class proficiency for Auto-fire. At level 7+, this also lowers the benefit from Weapon Specialization.

Let’s analyse a full round of firing a rotolaser and compare it with a magnetar rifle. With both weapons, we’ll do Primary target -> Auto-Fire against a single target -> simple Strike.

# Install in JupyterLite
%pip install -q pathfinder2e-stats

import matplotlib as mpl  # noqa: F401  # Needed by JupyterLite
import xarray

import pathfinder2e_stats as pf2
Note: you may need to restart the kernel to use updated packages.
martial_atk = pf2.tables.SIMPLE_PC.weapon_attack_bonus.soldier.sum("component")
advanced_atk = (
    martial_atk
    - pf2.tables.PC.weapon_proficiency.soldier
    + pf2.tables.PC.weapon_proficiency.weapon_proficiency
)

atk_by_level = xarray.concat(
    [martial_atk, advanced_atk],
    dim="weapon",
)
atk_by_level.coords["weapon"] = ["rotolaser", "magnetar_rifle"]
atk_by_level.display(transpose=True)
level 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
variable weapon
soldier rotolaser 6 8 9 10 14 15 16 17 18 20 21 22 23 24 28 30 31 32 33 34
magnetar_rifle 6 8 9 10 12 13 14 15 16 18 21 22 23 24 26 28 29 30 31 32
level = 5
atk = atk_by_level.sel(level=level)
area_fire_DC = (
    pf2.tables.SIMPLE_PC.area_fire_DC.soldier.sum("component").sel(level=level).item()
)
print(f"{area_fire_DC=}")
area_fire_DC=22
weapon_dice = pf2.tables.PC.weapon_dice.improvement.sel(level=level).item()
martial_weapon_specialization = pf2.tables.PC.weapon_specialization.soldier.sel(
    level=level
).item()
rotolaser = pf2.armory.starfinder.ranged.rotolaser(
    weapon_dice, martial_weapon_specialization
) + pf2.armory.upgrades.auto(level=level)

rotolaser
Damage 2d8 fire
# This is bespoke to the combination of class and Weapon Proficiency feat,
# so we need to handwrite it.
advanced_weapons_specialization = (
    xarray.DataArray(
        [0] * 10 + [2] * 10, dims=["level"], coords={"level": range(1, 21)}
    )
    .sel(level=level)
    .item()
)
magnetar_rifle = pf2.armory.starfinder.ranged.magnetar_rifle(
    weapon_dice, advanced_weapons_specialization
) + pf2.armory.upgrades.auto(level=level)

magnetar_rifle
Damage 2d12 piercing
enemy = pf2.tables.SIMPLE_NPC.sel(level=level, drop=True)[["AC", "saving_throws", "HP"]]
enemy.display()
variable AC saving_throws HP
challenge
Weak 16 6 31
Matched 21 12 75
Boss 25 18 148
# both 'weapon' and 'challenge' are what-if analyses - let's compare the same dice rolls
# against progressively harder-to-hit enemies.
pf2.set_config(
    check_dependent_dims=("challenge", "weapon"),
    damage_dependent_dims=("challenge",),
)
primary_target = pf2.check(atk, DC=enemy.AC)
primary_target = xarray.concat(
    [
        pf2.damage(primary_target.sel(weapon="rotolaser"), rotolaser),
        pf2.damage(primary_target.sel(weapon="magnetar_rifle"), magnetar_rifle),
    ],
    dim="weapon",
    join="outer",
    fill_value=0,
)
auto_fire = pf2.check(
    enemy.saving_throws, DC=area_fire_DC, primary_target=primary_target
)

auto_fire = xarray.concat(
    [
        pf2.damage(
            auto_fire.sel(weapon="rotolaser"),
            rotolaser.area_fire(),
        ),
        pf2.damage(
            auto_fire.sel(weapon="magnetar_rifle"),
            magnetar_rifle.area_fire(),
        ),
    ],
    dim="weapon",
    join="outer",
    fill_value=0,
)
# Note: Primary Target does not increase MAP, but Auto-Fire does
third_strike = pf2.check(atk - 5, DC=enemy.AC)
third_strike = xarray.concat(
    [
        pf2.damage(third_strike.sel(weapon="rotolaser"), rotolaser),
        pf2.damage(third_strike.sel(weapon="magnetar_rifle"), magnetar_rifle),
    ],
    dim="weapon",
    join="outer",
    fill_value=0,
)
full_round = xarray.concat([primary_target, auto_fire, third_strike], dim="action")
full_round["action"] = ["primary_target", "auto_fire", "third_strike"]

Chance to hit#

Note how the saving throw against auto-fire uses the same DC for both weapons, but it is influenced by the outcome of the Primary Target strike.

pf2.outcome_counts(full_round).stack(
    row=["action", "outcome"], col=["challenge", "weapon"]
).to_pandas()
challenge Weak Matched Boss
weapon rotolaser magnetar_rifle rotolaser magnetar_rifle rotolaser magnetar_rifle
action outcome
primary_target Critical success 0.44976 0.34996 0.19948 0.09888 0.04992 0.04992
Success 0.49971 0.49928 0.50144 0.50140 0.44966 0.34975
Failure 0.00000 0.10023 0.24855 0.34919 0.44989 0.44957
Critical failure 0.05053 0.05053 0.05053 0.05053 0.05053 0.15076
auto_fire Critical success 0.05018 0.05018 0.05018 0.05018 0.34809 0.34809
Success 0.00927 0.02831 0.14785 0.19820 0.25138 0.30083
Failure 0.63947 0.62043 0.75113 0.70078 0.34969 0.30024
Critical failure 0.30108 0.30108 0.05084 0.05084 0.05084 0.05084
third_strike Critical success 0.19714 0.09956 0.05050 0.05050 0.05050 0.05050
Success 0.50125 0.49898 0.39797 0.29790 0.19654 0.09796
Failure 0.25067 0.35052 0.45020 0.45026 0.45135 0.45008
Critical failure 0.05094 0.05094 0.10133 0.20134 0.30161 0.40146

Mean damage#

total_damage = full_round.total_damage.mean("roll")
total_damage = xarray.concat(
    [total_damage, total_damage.sum("action").expand_dims(action=["TOTAL"])],
    dim="action",
)
total_damage = total_damage.stack(col=["challenge", "weapon"]).to_pandas()
total_damage
challenge Weak Matched Boss
weapon rotolaser magnetar_rifle rotolaser magnetar_rifle rotolaser magnetar_rifle
action
primary_target 12.59890 15.60341 8.10443 9.09517 4.95016 5.84906
auto_fire 11.23759 16.10085 8.31895 11.70024 5.14770 7.13051
third_strike 8.06935 9.09409 4.49300 5.19927 2.68111 2.58713
TOTAL 31.90584 40.79835 20.91638 25.99468 12.77897 15.56670

Damage distribution#

bins = full_round.total_damage.max().item() + 1
_ = (
    full_round.total_damage.stack(col=["challenge", "weapon"])
    .sum("action")
    .to_pandas()
    .hist(bins=bins, sharex=True, figsize=(10, 10))
)
../_images/381d577bf9d6910c55c5d1a5a51ea1b7d4e628cabde14a78c37da7107e1aeb2a.png