Team Cereustooling wiki

Port a DS3 weapon art or ER skill

Partly checkedagainst the code on 2026-11-12 11:37

A player skill lives in three Nightreign files: c0000.anibnd (TAE events), c0000_a6x.anibnd (clips) and c0000.behbnd (the player behaviour graph). An Elden Ring skill needs its TAE converted and its clips copied. A Dark Souls III weapon art also needs its clips retargeted to the NR skeleton and usually new slots in the graph.

Checked against code: every command and spec format below. Not checked: the order of a full build (taken from the proof table in DS3-ARTS-IMPORT.md), the hks edits, the param rows and the FX and sound steps, which are still stage scripts. Nothing here was run or tested in game for this page.

EB means py -3.12 -I -B S:/eclipse-build/eb.py. Shared tools only: ../build/use-shared-tools.md.

What you need

  • The tools: EB tool build skillport behedit ds3retarget prints each folder (SkillPort.exe, BehEdit.exe, Ds3Retarget.exe).
  • Absolute paths for every file argument. All three resolve a relative path against their own folder, so a relative output lands inside the shared tool folder. SkillPort resolves against SKILLPORT_CWD if that variable is set.
  • The current release's c0000.anibnd.dcx, c0000_a6x.anibnd.dcx and c0000.behbnd.dcx as the base. If another stage of the same release already changes one of them, build on that stage's file and declare base_stage in stage.toml.
  • For an ER skill: the ER anibnd with the section, and SkillPort's rules.json. The rules file is not in eclipse-build. It was made by learn_rules.py in S:/eclipse-0084-stage/ds-weapons-084/tool/rules.
  • For a DS3 art: the DS3 c0000 anibnds, and a spec JSON for tae_ds3.py.

Port an Elden Ring skill

  1. Look at what is there:

    SkillPort ls   <bnd.dcx> [name filter]
    SkillPort dump <anibnd> <out.tsv> [section ...]
    

    dump writes one line per event: section, anim, header, clip, start, end, type, group type, params hex.

  2. Write a spec file with one line per TAE section: add <section> copies the ER section into the live binder (it refuses a section the live binder already has), convert <section> converts a live section in place. A section is named like a933.

  3. Convert:

    SkillPort port <rules.json> <spec.tsv> <ER anibnd> <live anibnd> <out anibnd> <log.tsv>
    

    The comment at the top of Program.cs leaves out <rules.json> and <log.tsv>. The code in Port.cs needs both. Every entry the spec does not touch is kept byte for byte and checked on read-back.

  4. Copy the clips:

    SkillPort hkx-add <out anibnd> <dst anibnd> <src anibnd>=<clip name> ...
    

    Clip bytes are not changed. A duplicate is refused.

  5. Add effect resources if the skill needs them:

    SkillPort res-add <out> <dst bnd> <src bnd>=<entry> | <loose file> ...
    

    Each entry gets the next free id of its file extension. A name the destination already has is refused.

  6. Fix single events where the automatic rules are not enough (next section).

SkillPort validate <rules.json> <ER anibnd> <NR anibnd> re-checks the rules against the sections both games ship. It is a check of the tool, not of your port.

What port changes

From the header of Port.cs. These are the rules FromSoftware followed when it converted its own ER player TAE for Nightreign, measured on the 191 skill sections both games ship:

  1. A whole-body ER event 605 becomes NR event 10150 {0, from, to, flags 0x101} with the same times, in group type 1005. A part 605 (index 3 to 6) stays 605.
  2. NR's own blend windows (10150 with 10 and flags 0x200 or 0x300) are copied from an NR anim that plays the same clip. If there is none, they are placed around the first 605 window.
  3. The parameter length of each event type becomes NR's. An attack event (type 1) grows from 16 to 32 bytes.
  4. Value rules from rules.json, for example SpEffect 45 becomes 31 in event 66.
  5. In event 712, an ER-only slot value (02,03) becomes the event's most common other slot value.
  6. A floor sound in event 129: ER sound type 8 id X becomes NR type 15 id X + 99999999.

Dated note (memory nr-er-tae-conversion, 2026-11-07): with these rules 93.3 % of NR's own events come out byte-identical. The rest is gameplay retuning that FromSoftware did by hand.

Edit single TAE events

All four take <in anibnd> <out anibnd> <spec.tsv>. Spec lines are tab separated; # starts a comment.

Command Spec line Rules
event-add section anim start end type params-hex Refuses an event the anim already has, so a second run fails loudly.
event-del same six columns Every line must match exactly one event. An event group left empty is removed.
event-edit section anim oldStart oldEnd type oldParams-hex newStart newEnd newParams-hex Old times match within 0.0001 s; old parameters must match exactly; each line must match one event.
anim-copy section dstAnim srcAnim The destination anim must be a slot that imports another anim's HKX. It becomes a copy of the source's events and imports the source's HKX.

Take the columns for a line straight from a SkillPort dump. SkillPort get <bnd> <out dir> <entry file name> ... writes binder entries out as files.

Port a DS3 weapon art

1. Retarget the clips

Write a config JSON for Ds3Retarget. Keys, from the Config class (names are not case sensitive, comments are allowed):

Key Meaning
Ds3Binders the DS3 c0000.anibnd (skeleton) and the c0000_aNx / _dlcN binders with the clips
NrAnibnd Eclipse's c0000.anibnd (skeleton)
NrA6x Eclipse's c0000_a6x.anibnd (compendium and template clip)
Template NR clip whose track layout the output copies. Default a713_040000.hkx
Out output folder
Identity bones that copy the DS3 world rotation as is
TransBones bones whose translation follows DS3. Default Master, RootPos
RootScale root motion scale. 0 uses the leg-length ratio
Helpers NR helper bone to its driving bone
LearnMax default 200
Clips list of { "Src": ..., "Dst": ..., "Speed": 1 }

Then:

Ds3Retarget bones <config.json>
Ds3Retarget build <config.json>
Ds3Retarget pack  <config.json> <a6x in> <a6x out>
Ds3Retarget info  <anibnd> <clip.hkx> [more binders]

bones writes the bone match to <out>/rep/bones.txt. build writes <out>/hkx/*.hkx and reports. pack adds or replaces the built clips in a copy of the a6x binder. --out <dir> on any of them overrides the config's Out, which is how a script builds twice into two folders. Two diagnostics: DR_LIST=1 lists the binder files, DR_ROOT=1 prints root motion of the DS3 clip and the retargeted clip.

2. Convert the TAE events

  1. Dump the DS3 section and an NR reference with SkillPort dump.

  2. Map the DS3 events with tools/arts-py/tae_ds3.py:

    py -3.12 -I -B tae_ds3.py <spec.json> <ds3 dump.tsv> <nr dump.tsv> <SpEffectParam.tsv> <fxr list> <out events.tsv> <log.txt> [fx map.tsv]
    
    • <fxr list> is a text file of fNNN.fxr names that Eclipse's sfx binders hold. An FX event whose FXR is missing is dropped.
    • [fx map.tsv] has old<TAB>new lines for DS3 FXRs this stage ports.
    • Spec keys the script reads: anims (each with src [section, anim], dst [section, anim], clip, optional header and noFp), judges, bulletJudges, spMap, soundMap, addedSpEffects, artFx, artSpEffects.
    • Every dropped event is written to the log. Read it.
  3. Write the events into the anibnd:

    Ds3Retarget tae-set <in anibnd> <out anibnd> <events.tsv>
    

    TSV columns: section anim header(std | imp:<anim id>) clip start end type group params-hex. It replaces the whole event list of each anim the TSV names. A line !new <aNNN> <aTTT> first creates section aNNN as an empty copy of section aTTT.

  4. Run SkillPort port with convert <section> lines for the new sections. It applies the ER to NR rules above, which is what turns the kept 605 events into 10150 and grows the attack events.

What tae_ds3.py does with DS3 events, from its header:

  • Most types exist in NR with the same layout and are kept byte for byte.
  • Attack (1) and bullet (2): the behaviour judge is remapped by judges / bulletJudges. A judge mapped to null drops the event.
  • FX (96): padded to 32 bytes; dropped if the FXR is in no Eclipse sfx binder.
  • SpEffect (66, 67): dropped if the id is not in SpEffectParam or addedSpEffects. Event 331 is a pair (SpEffect with FP, SpEffect without FP); the second slot is cleared.
  • Sound (128) becomes NR's player form 129. DS3 armour-material sounds (type 9) are dropped.
  • Types 117, 130 and 229 are dropped.

Use the frozen S:/eclipse-0086-stage/ds3-retarget-086/tool/tae_ds3.py only to reproduce 0.0.86's events. It keeps sound event 128, and those arts were silent in game.

3. Add graph slots

HKLib cannot read Nightreign's player graph, so BehEdit edits the file's bytes directly and proves on read-back that every original byte it did not mean to change is the same.

BehEdit probe <behbnd>
BehEdit show  <behbnd> <CMSG name regex>
BehEdit find  <behbnd> <animation name regex>
BehEdit apply <behbnd in> <spec.tsv> <behbnd out> <log.txt>

Run probe first. It must report a byte-identical round trip. show and find tell you which selector (CMSG) and template clip generator to copy.

apply spec lines, applied in order, # comments:

  • clip <CMSG name> <template clip-generator name> <new animation name>: a copy of the template clip generator that plays the new animation, appended to the CMSG.
  • section <src section aSSS> <dst section aDDD> [animation regex]: for every clip generator of section aSSS, a copy that plays aDDD_<same anim id>, added to every CMSG that lists the source.

A line that contains a TAB is split on tabs only, because some names hold a space. A line without a tab is split on blanks.

Other commands:

Command Arguments What it does
interval <behbnd in> <state machine name> <event name> <behbnd out> <log> sets the four interval event ids of that wildcard transition to -1
selfix <behbnd in> <state machine name> <state name> <selector name> <behbnd out> <log> names the state's selector and sets its two end-of-clip ids to -1
names <behbnd> <out tsv> every event, variable and state name
events <behbnd> [from id] counts, then event names from that id
clipcmp <behbnd> <anim regex A> <anim regex B> two clip generators side by side
wcdump <behbnd> <state machine name> every wildcard transition of the state machine
itemdump <behbnd> <item index> [depth] one object's members
files <behbnd> id, size and name of every binder file

names and selfix were not re-run in the import proof.

4. The rest

These steps are still per-stage scripts and are not shared tools yet (tools/README.md, "Waiting to be imported"):

  • hks: hks_patch.py in the art stages wires the new art type.
  • Param rows: rows_arts.py and rows_lc.py generate the rowlist. Apply it with Polish, see edit-params-and-rowlists.md.
  • Effects: the DS3 FXRs are ported with sfx_port.mjs, NpcPort sfxbnd-build and FxModelsGen.
  • Sounds: add-ds3-sounds.md.

How to tell it worked

  • Every tool prints a read-back summary and exits non-zero when the check fails.
  • SkillPort dump of the output shows your sections and events.
  • BehEdit show on the output lists the new clip generators under the CMSG.
  • Only a look in game proves the skill plays.

What goes wrong

  • A relative path writes into the shared tool folder. Use absolute paths.
  • A weapon buff shows nothing. Dated note (memory ds3-nr-retarget, 0.0.87): a buff must go from TAE event 331 through a per-hand carrier SpEffect to the buff row, like the vanilla buffs. A buff row straight from 331 shows nothing.
  • A sound posts many times. A full-body effect row spawns its FXR many times on the player and each copy posts its sound. Strip the sound from the FXR and post it once from TAE (tools/README.md).
  • SpEffect ids above 16,777,216 in hks round. See the note on port-a-ds3-or-er-character.md.
  • Never substitute a Nightreign look-alike effect for a DS3 one (team rule in the same memory note).
Source files this page documents (5)
  • eb:tools/dotnet/SkillPort/
  • eb:tools/dotnet/BehEdit/
  • eb:tools/dotnet/Ds3Retarget/Program.cs
  • eb:tools/arts-py/tae_ds3.py
  • eb:tools/dotnet/DS3-ARTS-IMPORT.md

Changed one of these? Update this page: ports/port-a-weapon-skill.md (how).