Build a loadout — SHD.build

BUILDER / LOADOUT 01

LOCAL SAVED {#if data.viewer} {#snippet children(pending)} {/snippet} {:else} SIGN IN TO SHARE {/if}
{#if data.viewer}
persistedVisibility = values[0] as BuildVisibility} required/>

{persistedVisibility === 'private' ? 'Private to you.' : persistedVisibility === 'unlisted' ? 'Direct link only.' : 'Public listing and link.'}

{#if persistedSlug && savedBuildVisibility !== 'private'}VIEW SHARE PAGE →{/if}
{/if}
STATS ONLINE
{#if message}

{message}

{/if} {#if form?.buildError}{/if} {#if data.viewer && persistedSlug}{/if} {#if !data.gear.length && !data.weapons.length && !data.chipSets.length && !data.protocols.length}
NO BUILDER DATAAdd active items in ISAC.
{/if}
active = id} />
{#if activeParts[0] === 'specialization'}
SPECIALIZATION

FOCUS

{selectedFocus ? `${selectedSpecializationPoints} / ${data.specializationProgression.availablePoints} POINTS` : 'UNASSIGNED'}
{#if selectedFocus}
{selectedFocus.specialization.name}

{selectedFocus.name}

{selectedFocus.description || 'No focus description.'}

{Number(selectedFocus.builder_ready) === 1 ? 'READY' : 'NEEDS DATA'}
{#each [0,1,2] as abilityIndex} {@const ability = selectedFocus.abilities[abilityIndex]} {/each}
POINTS {selectedSpecializationPoints} / {data.specializationProgression.availablePoints}CREDITS {selectedSpecializationCosts.credits.toLocaleString()}{#each selectedSpecializationCosts.resources as resource}{String(resource[0]).replace('resource-electronics-','').replaceAll('-',' ').toUpperCase()} × {resource[1]}{/each}
UPGRADE TREEUNLOCK IN ORDER
{selectedFocus.nodes.length} NODES
{#each [...new Set(selectedFocus.nodes.map((node: any) => Number(node.row_number)))] as row}
ROW {String(row).padStart(2, '0')}{row === 1 ? 'START' : 'PRIOR ROWS'}
{#each selectedFocus.nodes.filter((node: any) => Number(node.row_number) === row) as node}
{node.name}{node.description_template}
({value:String(index+1),label:`R${index+1}`,className:canSelectNodeRank(node,index+1)?'':'unavailable',disabled:!canSelectNodeRank(node,index+1)}))]} value={String(draft.specialization.nodeRanks[node.id] ?? 0)} onchange={(values) => setNodeRank(node,Number(values[0]))}/>
{/each}
{/each}
{/if} {:else if activeParts[0] === 'ability'} {@const abilityIndex = Number(activeParts[1])} {@const ability = selectedFocus?.abilities[abilityIndex]}
ABILITY {abilityIndex + 1}

{ability?.name?.toUpperCase() ?? 'UNASSIGNED'}

{draft.chips.slice(abilityIndex * 3, abilityIndex * 3 + 3).filter((chip) => chip.setId).length} / 3 CHIPS
{#if ability}
{selectedFocus.specialization.name} · {selectedFocus.name}

{ability.name}

{ability.description || 'No ability description.'}

{ability.attack ? `${ability.attack.delivery.toUpperCase()} · ${ability.attack.damage_type}` : 'ATTACK DATA NEEDED'}
SKILL CHIPS3 SETS
{draft.chips.slice(abilityIndex * 3, abilityIndex * 3 + 3).filter((chip) => chip.setId).length} / 3
{#each [0,1,2] as chipSlot}{@const chipIndex = abilityIndex * 3 + chipSlot}{@const item = chipItem(chipIndex)}{/each}
{:else}

Choose a focus with 3 abilities first.

{/if} {:else if activeParts[0] === 'gear'} {@const slot = activeParts[1]}{@const selection = draft.gear[slot]}{@const item = gearItem(slot)}
GEAR

{slot.toUpperCase()}

{item ? `T${item.tier} · LV ${selection.level}` : 'UNASSIGNED'}
entry.slot === slot)} bind:value={selection.itemId} onchange={(id) => configureGear(slot,id)} label="GEAR ITEM"/> {#if item} {@const healthRange = gearBaseRange(item, 'health', selection.level)} {@const armorRange = item.base_armor_min !== null ? gearBaseRange(item, 'armor', selection.level) : null} {@const gearCore = coreAllocationRange(Number(item.tier), selection.level, 'gear')} {@const augments = data.gearAugments.filter((entry) => entry.gear_slot === slot)}
changeGearLevel(slot,value)}/> brand(id)).filter(Boolean)} bind:value={selection.brandId} columns={Math.min(item.brandIds.length,4)} iconOnly required/> {#if item.talentIds.length} talent(id)).filter(Boolean)} value={selection.talentId} onchange={(id) => { selection.talentId = id; selection.talentTier = supportedTalentTier(id, selection.talentTier); }} columns={Math.min(item.talentIds.length,4)} searchable={item.talentIds.length > 6} required/>{/if} {#if selection.talentId && talent(selection.talentId)}{/if}

{healthRange.provisional ? 'Estimated range: levels 1–9 remain provisional.' : 'Base values scale with item level and use game-matching truncated display values.'}

{#if armorRange}{/if}
CORE ALLOCATIONSHARED T{item.tier} POOL
gearCore.poolMax}>{coreTotal(selection)} / {gearCore.poolMin}–{gearCore.poolMax}
{#each coreKeys as key}{/each}
ROLLED ATTRIBUTESTWO REQUIRED SLOTS
{#each selection.rolls as roll, index}{@const rollRange = attributeRange(attribute(roll.statId), 'gear', selection.level)}
setRollStat(roll,id,'gear',selection.level)} disabledIds={selection.rolls.filter((_, itemIndex) => itemIndex !== index).map((entry) => entry.statId).filter(Boolean)} label={`ATTRIBUTE ${index + 1}`}/>
{/each}
{#if augments.length}
TACTICAL AUGMENTSOPTIONAL ITEM UPGRADES
{selection.augmentSlots.length} / {augments.length} ACTIVE
{#each augments as augment}{/each}
{/if}
{/if} {:else if activeParts[0] === 'weapon'} {@const index = Number(activeParts[1])}{@const selection = draft.weapons[index]}{@const item = weaponItem(index)}{@const other = weaponItem(index ? 0 : 1)}
WEAPON

{index ? 'SECONDARY' : 'PRIMARY'}

{item ? item.damage_type : 'UNASSIGNED'}
configureWeapon(index,id)} disabledIds={other && Number(other.is_exotic) ? data.weapons.filter((entry) => Number(entry.is_exotic)).map((entry) => entry.id) : []} label="WEAPON"/> {#if item} {#if Number(item.is_exotic) && other && Number(other.is_exotic)}

Only one exotic weapon may be equipped. Choose a standard weapon in either slot.

{/if} {@const range = item.ranges.find((entry: any) => Number(entry.tier) === selection.tier)} {#if range}{@const damageRange = weaponStatRange(range,'damage',selection.level)}{@const weaponCore = coreAllocationRange(selection.tier, selection.level, 'weapon')}
changeWeaponLevel(index,value)}/> ({ value: String(entry.tier), label: `T${entry.tier}` }))} value={String(selection.tier)} onchange={(values) => changeWeaponTier(index,Number(values[0]))} required/>
{item.rpm} RPM{item.magazine} SHOTS{item.reload_speed}s RELOAD

{selection.level < 40 ? 'Estimated range below level 40.' : 'Damage scales with the selected item level.'}

{#if item.baseBonuses.length}
{#each item.baseBonuses as bonus}{attribute(bonus.stat_attribute_id)?.name}{unitValue(bonus.value,bonus.stat_attribute_id)}{/each}
{/if}
CORE ALLOCATIONSHARED T{selection.tier} POOL
weaponCore.poolMax}>{coreTotal(selection)} / {weaponCore.poolMin}–{weaponCore.poolMax}
{#each coreKeys as key}{/each}
ROLLED ATTRIBUTESTWO REQUIRED SLOTS
{#each selection.rolls as roll, rollIndex}{@const rollRange = attributeRange(attribute(roll.statId), 'weapon', selection.level)}
setRollStat(roll,id,'weapon',selection.level)} disabledIds={selection.rolls.filter((_, itemIndex) => itemIndex !== rollIndex).map((entry) => entry.statId).filter(Boolean)} label={`ATTRIBUTE ${rollIndex + 1}`}/>
{/each}
{#if item.talent1Ids.length}
talent(id)).filter(Boolean)} value={selection.talent1Id} onchange={(id) => { selection.talent1Id = id; selection.talent1Tier = supportedTalentTier(id, selection.talent1Tier); }} columns={Math.min(item.talent1Ids.length,4)} searchable={item.talent1Ids.length > 6} required/>{#if selection.talent1Id && talent(selection.talent1Id)}{/if}
{/if} {#if item.talent2Ids.length}
talent(id)).filter(Boolean)} value={selection.talent2Id} onchange={(id) => { selection.talent2Id = id; selection.talent2Tier = supportedTalentTier(id, selection.talent2Tier); }} columns={Math.min(item.talent2Ids.length,4)} searchable={item.talent2Ids.length > 6} required/>{#if selection.talent2Id && talent(selection.talent2Id)}{/if}
{/if} {#if item.exotic_talent_id && talent(item.exotic_talent_id)}
EXOTIC TALENT{talent(item.exotic_talent_id).name}
{/if}
{/if} {/if} {:else if activeParts[0] === 'chip'} {@const index = Number(activeParts[1])}{@const chip = draft.chips[index]}{@const item = chipItem(index)}{@const abilityIndex = Math.floor(index / 3)}{@const ability = selectedFocus?.abilities[abilityIndex]}
CHIP {(index % 3) + 1} / 3

{ability?.name?.toUpperCase() ?? 'SKILL CHIP'}

{chip.quality.toUpperCase()}
{#if ability} chipSupportsAbility(entry, ability.id))} bind:value={chip.setId} onchange={(id) => configureChip(index,id)} label="CHIP SET"/>{:else}

Choose a focus with 3 abilities first.

{/if} {#if !chipProfileConfigured}

CHIP PROFILE REQUIRED. Configure shared ranges in ISAC.

{/if} {#if item}
({ value: quality, label: quality.toUpperCase() }))} value={chip.quality} onchange={(values) => changeChipQuality(index, values[0] as SkillChipQuality)} required/>
PRIMARYABILITY ONLY
NOT IN TOTALS

Not included in totals.

setChipRoll(chip.primary,id,'primary',chip.quality)} label="PRIMARY"/>{#if validChipRange(chipRange('primary', chip.primary.statId, chip.quality))}{:else}

PRIMARY RANGE REQUIRED

{/if}
SECONDARIES{skillChipSecondaryCapacity[chip.quality]} SLOT{skillChipSecondaryCapacity[chip.quality] === 1 ? '' : 'S'}
{#each chip.secondary as roll, secondaryIndex}
setChipRoll(roll,id,'secondary',chip.quality)} disabledIds={chip.secondary.filter((_,i) => i !== secondaryIndex).map((entry) => entry.statId).filter(Boolean)} label={`SECONDARY ${secondaryIndex + 1}`}/>{#if validChipRange(chipRange('secondary', roll.statId, chip.quality))}{:else}

SECONDARY RANGE REQUIRED

{/if}
{/each}
{#if skillChipSecondaryCapacity[chip.quality] === 0}

NO SECONDARIES

{/if}
{/if} {:else} {@const protocol = protocolItem()}
PROTOCOL

OS PROTOCOL

{protocol?.quality?.toUpperCase() ?? 'UNASSIGNED'}
{#if protocol}
{#if protocol.primaryEffect}{attribute(protocol.primaryEffect.stat_attribute_id)?.name} · LEVEL {draft.protocol.level}{unitValue(Number(protocol.primaryEffect.base_value)*(1+(draft.protocol.level-1)/49),protocol.primaryEffect.stat_attribute_id)}{/if}{#each protocol.secondaryEffects as effect}{attribute(effect.stat_attribute_id)?.name}{unitValue(effect.value,effect.stat_attribute_id)}{/each}
{/if} {/if}