Skip to content

Add BeastmasterGauge - #1947

Open
wangel wants to merge 4 commits into
aers:mainfrom
wangel:beastmaster-gauge
Open

wangel wants to merge 4 commits into
aers:mainfrom
wangel:beastmaster-gauge

Conversation

@wangel

@wangel wangel commented Sep 9, 2026

Copy link
Copy Markdown

Adds BeastmasterGauge (ClassJobId 43).

Mapped at runtime on 7.56 with a dalamud plugin I wrote that dumps every frame and
tracks per-byte min/max/distinct, then confirmed each field against the HUD or the combat log.

Offset Field Notes
0x08 TPGauge 0-250. No passive regen; granted by weaponskill combo bonuses. Instinctual skills spend the whole pool.
0x09 FamiliarTPGauge 0-250. One pool shared across Battlehorn slots, not per-familiar. Arrives in +12 steps from the familiar's auto-attacks.
0x0A FamiliarTPAtLastUse Familiar TP immediately before the last familiar action, which scales its potency.
0x0B ActiveBattlehorn 0-3. The slot, not the creature.
0x0C InstinctualComboState Affinity, then 7 while Wavering Heart (Status 4643) locks out further combo advancement.
0x0D CurrentAffinity BeastmasterAffinity, 0-6. Clears ~7s after it is applied.
0x0E ChainCount Chained instinctual skills, matching the number drawn under the Inner Compass.
0x0F KinshipState Two nibbles: kin type high, Battlehorn slot low.
0x10 InstinctState Two 2-bit counters: Natural Instinct low, Mastered Instinct high. Trait-gated, reads 0 below level 28.

BeastmasterAffinity has six values the four compass affinities plus the two combo results:

None 0  Volant 1  Rampant 2  Durant 3  Eldritch 4  Sunstrider 5  Moonstalker 6

The compass runs clockwise Volant -> Rampant -> Durant -> Eldritch -> Volant, and a clockwise pair inside the 7s window is what makes a combo.

Sunstrider and Moonstalker are the results are the affinity you started from. Odd = Sunstrider, Even = Moon.

The Status sheet backs this up. The six statuses the job applies are in this order (4595-4600).

The upgrade only lands if the target survives the combo.

0x10 holds two resources in one byte. Mastered Instinct (bits 2-3, trait Wild Heart III at
level 28) is gained when a you complete a combo...ie; You use tricks and then an axe skill. Rally is the spender.

Natural Instinct (bits 0-1, Wild Heart IV at level 40) is gained when a pet completes a combo....ie; you use an axe skill, then tricks. It's spent using Rallying Cheer.

Each caps at 3, each spender clears only its own bits.

Thanks to @Taurenkey for identifying the 0x0C 7 as Wavering Heart, and to @Haselnussbomber
for XBMManager and ActionManager.BeastmasterPets, which is where the familiar's identity
lives - the gauge only exposes the slot.

@Taurenkey

Copy link
Copy Markdown
Contributor

The part you're not sure about should relate to the status you get as a player, when it's set to 7 you have the "Wavering Heart" status which prevents further advancement to the combo until it's cleared. Most likely to prevent triple weaving shenanigans breaking the gauge or something.

Beastmaster (ClassJobId 43) had no gauge struct. Mapped at runtime on patch
7.56 by watching JobGaugeManager while playing the job, with every offset
confirmed against the in-game display, ability tooltips or the combat log.

Fields, CS 0x08 through 0x0F:

  TPGauge               player TP; granted by weaponskill combo bonuses
                        (Axeblade Bite +13, Shieldsplitter +15), not regen
  FamiliarTPGauge       familiar TP; one pool shared across Battlehorn
                        slots, arriving in +12 steps from auto-attacks
  FamiliarTPAtLastUse   familiar TP at the last familiar action, which
                        scales its potency; actions spend the whole pool
  ActiveBattlehorn      summoned Battlehorn slot, not the creature
  InstinctualComboState active affinity, or 7 while the Wavering Heart
                        status locks out further combo advancement
  CurrentAffinity       BeastmasterAffinity
  ChainCount            chained instinctual skills
  KinshipState          packed (kin type << 4) | Battlehorn slot, latched
                        when Borrow grants a Kinship

Adds BeastmasterAffinity, whose six values match the contiguous status rows
4595 Volant Heart through 4600 Moonstalker. An intentional combo is the next
affinity clockwise, and which of the two extra affinities it yields is the
parity of the affinity it started from, so no lookup table is needed. The
upgrade requires the target to survive the combo.

Adds BeastmasterKinType, whose eight values match the Kinship status rows
4602 Beast through 4609 Ash. All eight were observed directly in the gauge
and cross-checked against the Kinship each familiar actually granted.
@wangel

wangel commented Sep 9, 2026

Copy link
Copy Markdown
Author

That's it exactly, thank you!!!!

"Otherwise engaged. Unable to perform combos with your familiar."

It also explains something that had been nagging me. I'd measured that hold at 2.22–2.23s and got the same number on every combo across three different pairings, which felt far too clean for a resolution delay. It isn't one, it's a fixed status duration.

Chasing it turned up a related bit of behaviour worth recording: if the second skill kills the target, you still get Wavering Heart and the ChainCount increment, but the affinity never upgrades to Sunstrider/Moonstalker. That had me convinced I had something broken.

Pointing me at the status side was worth doing twice over, because going back to the sheet turned up two more things.

First, the six affinities are contiguous and in the same order as the gauge values (4595 Volant Heart → 4600 Moonstalker), which independently confirms Rampant = 2 — the one value I had gotten from the compass position rather than actually observed. Sunstrider and Moonstalker also each say that using a skill with the other's affinity triggers an "infinitive combo", so there's a tier above intentional that pairs 5 against 6.

Second, and bigger: I'd written 0x0F off as a timer that goes up. It isn't a timer at all — I was reading it as a decimal. In hex it's two nibbles, high is the familiar's kin type and low is the Battlehorn slot it came from. I summoned one familiar of each kin and used Borrow:

0x11 Beast/1   0x12 Beast/2   0x83 Ash/3     0x51 Wave/1
0x32 Cloud/2   0x73 Soul/3    0x62 Scale/2   0x41 Seed/1
0x21 Vile/1

All eight kin types, each matching the familiar I had out and the Kinship status I actually got, numbered the same as the Kinship block at 4602–4609. So the branch now carries KinshipState and a BeastmasterKinType enum — kin type is in the gauge after all, which the original body said it wasn't.

While I was at it I also had to correct both TP fields, since neither gauge regenerates: player TP comes from the weaponskill combo bonuses (Axeblade Bite +13, Shieldsplitter +15), which averages neatly to the "~14 a tick" I had wrongly written down.

Branch and body are both updated.

@wangel

wangel commented Sep 9, 2026

Copy link
Copy Markdown
Author

Two updates and a thank-you.

Rampant = 2 is no longer inferred. I hooked UseAction in my dump plugin and attributed
each 0x0D transition to the press just before it. Avalanche Axe applies 2 directly, so every
value in the enum has now been seen in the gauge rather than derived from Status sheet ordering.
I've corrected that line in the description above.

The same attribution mapped the four standalone axes, one per compass affinity:

action affinity
44884 Avalanche Axe Rampant (2)
44887 Mistral Axe Durant (3)
44888 Spinning Axe Eldritch (4)
44889 Gale Axe Volant (1)

These are fixed per action — the affinity is shown on the actions in game, and only the
familiar's varies. Nothing this PR needs, but it is independent confirmation that the enum
ordering is real rather than a coincidence of the Status sheet: the four axes cover all four
affinities, so there is always one at beast affinity + 1 and an intentional combo is available
whatever familiar is out.

The familiar's side resolves through the XBMPet sheet rather than the creature: affinity is a
property of the pet's AutoAttack action (see the sheet columns below), not of the pet
or its kin. Cu Sith and Puk share
AutoAttack 45187 and both apply Rampant despite differing in kin, Pet id and notebook
slot. Roughly 15 action ids cover all 50 pets. The unnamed 0x1A is not the affinity - Cu Sith
is 3 and Puk is 2, and both give Rampant.

Thanks @Haselnussbomber for XBMManager (4aa267b) — that joined up two things I had been
poking at separately. Some runtime detail back, in case it is useful to whoever writes the
binding for ActionManager::GetBeastmasterPet (0x1408F0590, named in the same commit):

  • the argument is a Battlehorn slot, 0-based — 0, 1, 2. (Correction to an earlier draft of this:
    it is bounds-checked and returns 0 above index 2. I had called it through a 64-bit signature and
    was reading stale register bits above the returned byte.)
  • the return is the Monster Notebook position, 1-based: 1 for a familiar sitting at notebook
    slot 1, 20 for one at slot 20.
  • those values are the id IsPetUnlocked takes, and the bit index is petId - 1.
  • BNpcBase BaseId = petId + 18915 held on all five familiars I tried, across petId 1–20.
    PetBuddy.DataID does not track it consistently — one familiar is off by one, so BaseId
    is the field that stays in step.

On XBMPet (51 rows, row 0 null): I had worked out some columns by matching the sheet against
familiars I had summoned, but nt153133 kindly sent me the SaintCoinach definition, which names
them properly and corrects two of my guesses. In dump order:

Description, TrickDescription, TemperedReleaseDescription, Icon, Pet, AutoAttack,
NaturalHabitat, Classification, two unnamed bytes at 0x19 and 0x1A, NaturalHabitatType.

Pet is the link to the Pet sheet - that is the id IBuddyList.PetBuddy.DataID gives you.
AutoAttack is an Action row. NaturalHabitat is a complexlink: PlaceName when
NaturalHabitatType is 1, ContentFinderCondition when it is 2. What I had taken for a tier was
the habitat type, and what I had taken for a text reference was the icon.

Classification is the kin type, 1-8 Beast/Vile/Cloud/Seed/Wave/Scale/Soul/Ash - that one I had
right, and it matches the gauge's 0x0F high nibble exactly.

One correction I guessed XBMModule was where the
slot→creature mapping lived. GetBeastmasterPet is the actual answer.

Mastered Instinct and Natural Instinct share one byte at 0x10, two bits
each. Both are trait-gated, so the byte reads 0 below level 28 and only
the upper pair moves until level 40 - which is why an earlier revision of
this PR reported everything from 0x10 on as unused.
@github-actions github-actions Bot added the requested changes Changes requested through comments label Sep 10, 2026
Size was 0x10, which only allows offsets up to 0x0F, so the new field at
0x10 tripped CExporter. Rounded up to the next 8-byte boundary to match
the other gauges in this file.
@github-actions github-actions Bot removed the requested changes Changes requested through comments label Sep 10, 2026
@wolfcomp wolfcomp added the AI This PR has been created with an AI summary and/or AI commits. label Sep 10, 2026
@wangel

wangel commented Sep 11, 2026

Copy link
Copy Markdown
Author

Rewrote the description in my own words - the original was written by AI was slop, I was being lazy with it. The data was all gathered from a plugin I wrote, that I used while testing and playing. If you any of you want the plugin to see it, hit me up and I'll share it.

@wolfcomp wolfcomp left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still feels like AI written.

Comment on lines +239 to +266
// Identifies the familiar whose Kinship is currently active: high nibble is its kin type,
// low nibble is the Battlehorn slot it was summoned from. Set when Borrow grants a Kinship
// and cleared to 0 when that Kinship is lost. Latched at the moment Borrow is used, so the
// low nibble is NOT a mirror of ActiveBattlehorn - swapping familiars afterwards moves
// ActiveBattlehorn while this stays put.
[FieldOffset(0x0F)] public byte KinshipState;
// Two instinct stack counters packed into one byte, two bits each, both gated behind
// traits - this reads 0 below level 28 and only the upper pair moves until level 40.
//
// bits 0-1 Natural Instinct (trait Wild Heart IV, Lv 40) - gained when a FAMILIAR
// instinctual skill completes the combo. Spent by Rallying Cheer (44904) for
// 30 familiar TP plus 70 per stack.
// bits 2-3 Mastered Instinct (trait Wild Heart III, Lv 28) - gained when a BEASTMASTER
// instinctual skill completes the combo. Spent by Rally (44905) for 40 TP plus
// 70 per stack, so three stacks fills an empty 250 gauge exactly. The excess is
// clamped rather than scaled: three stacks spent while holding 28 TP still
// landed on 250.
//
// Each field caps at 3, giving a maximum byte of 15. Observed as 8 -> 9 -> 13 -> 14 -> 15
// across four combos, gaining 4 whenever the player's skill completed the combo and 1
// whenever the familiar's did. Each spender clears only its own bits: Rally with both fields
// full took this from 15 to 3 while TPGauge went 0 -> 250.
[FieldOffset(0x10)] public byte InstinctState;

public BeastmasterKinType KinshipKinType => (BeastmasterKinType)(KinshipState >> 4);
public byte KinshipBattlehorn => (byte)(KinshipState & 0x0F);
public byte NaturalInstinct => (byte)(InstinctState & 0x03);
public byte MasteredInstinct => (byte)((InstinctState >> 2) & 0x03);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All of this could be done with the bitfield attribute and check how it is accessed in the exe as it could reveal more detail about how each field is accessed.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed --- using the bitfield attribute. I don't have a decompiler installed, everything was done during runtime in the game. I will get something installed.

Comment thread FFXIVClientStructs/FFXIV/Client/Game/Gauge/JobGauges.cs Outdated
Comment thread FFXIVClientStructs/FFXIV/Client/Game/Gauge/JobGauges.cs Outdated
Comment thread FFXIVClientStructs/FFXIV/Client/Game/Gauge/JobGauges.cs Outdated
Comment thread FFXIVClientStructs/FFXIV/Client/Game/Gauge/JobGauges.cs Outdated
Comment thread FFXIVClientStructs/FFXIV/Client/Game/Gauge/JobGauges.cs Outdated
Comment on lines +223 to +230
// Reads the active affinity, then 7 while an instinctual combo resolves, then - if the
// pair was clockwise and so formed an intentional combo - the resulting Sunstrider or
// Moonstalker affinity ~2.2s later. If the pair was not clockwise it returns to 0 from 7
// instead. The 7 is not an affinity: it marks the "Wavering Heart" status (Status row
// 4643, "Otherwise engaged. Unable to perform combos with your familiar."), which locks
// out further combo advancement until it clears. That is why this stays a byte while
// CurrentAffinity below is typed as the enum.
[FieldOffset(0x0C)] public byte InstinctualComboState;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check how this is actually accessed and stored in the exe and remove the comment as it is not needed to be this long.

@wangel wangel Sep 11, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will work on getting a decompiler installed. Everything I did came from in the game during runtime.

Comment thread FFXIVClientStructs/FFXIV/Client/Game/JobGaugeManager.cs Outdated
Comment thread FFXIVClientStructs/FFXIV/Client/Game/Gauge/JobGaugeEnums.cs Outdated
Comment thread FFXIVClientStructs/FFXIV/Client/Game/Gauge/JobGaugeEnums.cs Outdated
@redstrate

redstrate commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

On the comment point: comments like that are so hilariously long and novel-ish tend to make the code unreadable instead of understandable. They need to be cut entirely - as suggested already - or written more concisely, CS doesn't need to be a job tutorial. That also applies to the commit message pre and post AI TBH, but I guess that's more of a personal opinion.

The real juice here is how and any quirks of how the data is stored that wouldn't be immediately obvious from the variable name or type alone, what wolfcomp suggests is going to be much better than manual documentation on every little nibble and bit.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

AI This PR has been created with an AI summary and/or AI commits.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants