import type { Address } from '@solana/kit'; import { Faction } from '@staratlas/dev-profile-faction'; import { FixedPoint } from '../src/fixed'; import { getU16F48Codec, getU32F32Codec } from '../src/fixed/codecs'; import { getFleetCodec as getFleetAccountCodec, getLootCodec, getScanPatternCodec as getScanPatternAccountCodec, getStarSystemCodec as getStarSystemAccountCodec, type FleetArgs, type LootArgs, type ScanPatternArgs, type StarSystemArgs, } from '../src/generated/accounts'; import { getAsteroidResourceStateCodec, getBoundingBoxCodec, getBuildingDefinitionCodec, getBuildingIdCodec, getBuildingTagCodec, getCargoCategoryIdCodec, getCargoIdCodec, getCargoStatsCodec, getCelestialBodyIdCodec, getCharacterModifierCodec, getClaimStakeDefinitionCodec, getClaimStakeDefinitionIdCodec, getCombatConfigCodec, getCombatStatsCodec, getCraftingHabDefinitionCodec, getCraftingHabDefinitionIdCodec, getCreateRecipeInputDataCodec, getCreateScanPatternArgsCodec, getHabBuildingDefinitionCodec, getJourneyCodec, getLocationTagCodec, getMineAsteroidCodec, getMinMaxCodec, getMiscStatsCodec, getMovementStatsCodec, getMoveWarpCodec, getNameCodec, getPilotXpBudgetCodec, getPlanetCodec, getRecipeIdCodec, getRegionCodec, getRegionIdCodec, getResearchNodeIdCodec, getResearchTagCodec, getScanCostCodec, getScanLootEntryCodec, getScanNoiseMapBorshCodec, getScanNoiseMapCodec, getScanPatternIdCodec, getShipIdCodec, getStarbaseCodec, getStarbaseLevelInfoCodec, getSystemIdCodec, getUpdateRecipeInputDataCodec, getUpdateScanPatternArgsCodec, getUpdateStarSystemArgsCodec, riskZone, SafetyStatus, ScanPatternStatus, StarbaseLevel, starbaseMaxLevel, type AsteroidResourceStateArgs, type CargoStatsArgs, type CharacterModifierArgs, type ClaimStakeDefinitionArgs, type CombatConfigArgs, type CombatStatsArgs, type CraftingHabDefinitionArgs, type CreateRecipeInputDataArgs, type CreateScanPatternArgsArgs, type HabBuildingDefinitionArgs, type MiscStatsArgs, type MovementStatsArgs, type ScanCostArgs, type ScanLootEntryArgs, type ScanNoiseMapArgs, type ScanNoiseMapBorshArgs, type StarbaseLevelInfoArgs, type UpdateRecipeInputDataArgs, type UpdateScanPatternArgsArgs, type UpdateStarSystemArgsArgs, } from '../src/generated/types'; type OptionSome = Readonly<{ value: T }>; type TaggedOptionSome = Readonly<{ __option: 'Some'; value: T }>; type TaggedOptionNone = Readonly<{ __option: 'None' }>; function getCombatStatsArgs( overrides: Partial = {}, ): CombatStatsArgs { return { ap: 0, hp: 0, sp: 0, ammoConsumptionRate: 0, apRegenRate: 0, shieldRechargeRate: 0, repairRate: 0, repairAbility: 0, repairEfficiency: 0, repairCost: 0, lootRate: 0, shieldBreakDelay: 0, warpSpoolTime: 0, damageKinetic: 0, damageKineticVariance: 0, damageEnergy: 0, damageEnergyVariance: 0, damageEmp: 0, damageEmpVariance: 0, damageSuperchill: 0, damageSuperchillVariance: 0, damageShockwave: 0, damageShockwaveVariance: 0, damageGraygoo: 0, damageGraygooVariance: 0, damageHeat: 0, damageHeatVariance: 0, damageBomb: 0, damageBombVariance: 0, counterDecoy: 0, counterEnergyCapacitor: 0, counterFireSuppressor: 0, counterFlare: 0, counterHealingNanobots: 0, counterMine: 0, counterNegativeRemPlating: 0, counterWarmingPlates: 0, counterFaradayShielding: 0, stealthPower: 0, hitChance: 0, aimAbility: 0, critChance: 0, critMultiplier: 0, fuelConsumptionRate: 0, missileDamage: 0, missileFireRate: 0, ...overrides, }; } function unwrapOptionValue( option: T | null | OptionSome | TaggedOptionSome | TaggedOptionNone, ): T | null { if (option === null) { return null; } if (typeof option === 'object') { if ('__option' in option) { return option.__option === 'Some' ? option.value : null; } if ('value' in option) { return option.value; } } return option; } describe('Codama visitors', () => { it('encodes name as fixed 64-byte utf8', () => { const codec = getNameCodec(); const value = 'Star Atlas'; const encoded = codec.encode(value); const utf8Bytes = new TextEncoder().encode(value); expect(encoded).toHaveLength(64); expect([...encoded.slice(0, utf8Bytes.length)]).toEqual([...utf8Bytes]); expect([...encoded.slice(utf8Bytes.length)]).toEqual( Array(64 - utf8Bytes.length).fill(0), ); expect(codec.decode(encoded)).toBe(value); }); it('preserves the 16-byte Pilot XP budget layout for legacy and fractional values', () => { const codec = getPilotXpBudgetCodec(); const legacyBytes = new Uint8Array(16); const legacyView = new DataView(legacyBytes.buffer); legacyView.setBigInt64(0, 1_000_000n, true); legacyView.setBigUint64(8, 360n, true); expect(codec.fixedSize).toBe(16); expect(codec.decode(legacyBytes)).toEqual({ lastAccrualTime: 1_000_000n, available: 360, fractionalCarry: [0, 0, 0, 0, 0, 0], }); // Q0.48 0.5 is bit 47, the high bit of the sixth carry byte. const carry = [0, 0, 0, 0, 0, 0x80]; const encoded = codec.encode({ lastAccrualTime: 1_000_240n, available: 359, fractionalCarry: carry, }); expect(encoded).toHaveLength(16); expect(codec.decode(encoded)).toEqual({ lastAccrualTime: 1_000_240n, available: 359, fractionalCarry: carry, }); }); it.each([ ['BuildingId', getBuildingIdCodec], ['BuildingTag', getBuildingTagCodec], ['CargoCategoryId', getCargoCategoryIdCodec], ['CargoId', getCargoIdCodec], ['CelestialBodyId', getCelestialBodyIdCodec], ['ClaimStakeDefinitionId', getClaimStakeDefinitionIdCodec], ['CraftingHabDefinitionId', getCraftingHabDefinitionIdCodec], ['LocationTag', getLocationTagCodec], ['RecipeId', getRecipeIdCodec], ['RegionId', getRegionIdCodec], ['ResearchNodeId', getResearchNodeIdCodec], ['ResearchTag', getResearchTagCodec], ['ScanPatternId', getScanPatternIdCodec], ['ShipId', getShipIdCodec], ['SystemId', getSystemIdCodec], ])('%s encodes as u16', (_, getCodec) => { const codec = getCodec(); const value = 0xabcd; const encoded = codec.encode(value); expect([...encoded]).toEqual([0xcd, 0xab]); expect(encoded).toHaveLength(2); expect(codec.decode(encoded)).toBe(value); }); it('planet grids encode U32F32 fees and U16F48 resources', () => { const u32f32 = FixedPoint.fromNumber(1.5, { fractionalBits: 32, totalBits: 64, signed: false, }); const u16f48 = FixedPoint.fromNumber(2.0, { fractionalBits: 48, totalBits: 64, signed: false, }); const claimStakePlots = Array.from({ length: 7 }, () => Array.from({ length: 5 }, () => 0), ); const feeGrid = Array.from({ length: 7 }, () => Array.from({ length: 5 }, () => u32f32), ); const planet = { claimStakePlots, baseClaimStakeRentRate: feeGrid, baseClaimStakePlacementFee: feeGrid, resources: new Map([[1, u16f48]]), providedTags: new Set([1]), claimedPlots: { unsizedLen: 0, offsetList: [], unsizedList: [], }, }; const codec = getPlanetCodec(); const bytes = codec.encode(planet); const decoded = codec.decode(bytes); expect(getU32F32Codec().decode(getU32F32Codec().encode(u32f32)).raw).toBe( u32f32.raw, ); expect(getU16F48Codec().decode(getU16F48Codec().encode(u16f48)).raw).toBe( u16f48.raw, ); decoded.baseClaimStakeRentRate.forEach((row) => row.forEach((fp) => expect(fp.raw).toBe(u32f32.raw)), ); decoded.resources.forEach((val) => { expect(val.raw).toBe(u16f48.raw); }); }); it('starbase hab fees encode as U32F32 grids', () => { const u32f32 = FixedPoint.fromNumber(2.5, { fractionalBits: 32, totalBits: 64, signed: false, }); const feeGrid = Array.from({ length: 7 }, () => Array.from({ length: 5 }, () => u32f32), ); const levelInfo = { hp: 0, sp: 0, xpValue: 0, shieldBreakDelay: 0, shieldRechargeRate: 0, repairRate: 0, repairAbility: 0, repairEfficiency: 0, repairCost: 0, repairFee: 0n, damagePerRound: 0, }; const starbase = { owner: Faction.Mud, level: StarbaseLevel.Level0, localMarketEnableLevel: StarbaseLevel.Level0, repairEnableLevel: StarbaseLevel.Level0, hp: 0, pendingHp: 0, sp: 0, shieldBreakDelayExpiresAt: 0n, lastCombatUpdate: 0n, builtDestroyedTimestamp: 0n, craftingHabPlots: Array.from({ length: 7 }, () => Array(5).fill(0)), baseCraftingHabRentRate: feeGrid, baseCraftingHabPlacementFee: feeGrid, starbaseMaxLevel: starbaseMaxLevel('Level0', [levelInfo]), locationTags: new Set([1]), buildingTags: new Set([1]), validRecipes: new Set([1]), craftingHabs: { unsizedLen: 0, offsetList: [], unsizedList: [], }, }; const codec = getStarbaseCodec(); const decoded = codec.decode(codec.encode(starbase)); decoded.baseCraftingHabRentRate.forEach((row) => row.forEach((fp) => expect(fp.raw).toBe(u32f32.raw)), ); decoded.baseCraftingHabPlacementFee.forEach((row) => row.forEach((fp) => expect(fp.raw).toBe(u32f32.raw)), ); }); it('StarbaseLevelInfo encodes fixed-point stats', () => { const xp = FixedPoint.fromNumber(12.5, { fractionalBits: 16, totalBits: 32, signed: false, }); const rate = FixedPoint.fromNumber(3.75, { fractionalBits: 6, totalBits: 32, signed: false, }); const args: StarbaseLevelInfoArgs = { hp: 1, sp: 2, xpValue: xp, shieldBreakDelay: 3, shieldRechargeRate: rate, repairRate: rate, repairAbility: rate, repairEfficiency: rate, repairCost: rate, repairFee: 4n, damagePerRound: 5, }; const codec = getStarbaseLevelInfoCodec(); const decoded = codec.decode(codec.encode(args)); expect(decoded.xpValue.raw).toBe(xp.raw); expect(decoded.shieldRechargeRate.raw).toBe(rate.raw); expect(decoded.repairCost.raw).toBe(rate.raw); }); it('CombatConfig encodes fixed-point fields', () => { const range = FixedPoint.fromNumber(100.5, { fractionalBits: 56, totalBits: 64, signed: true, }); const lootRate = FixedPoint.fromNumber(1.25, { fractionalBits: 6, totalBits: 32, signed: false, }); const upgradeContinuation = FixedPoint.fromNumber(0.5, { fractionalBits: 32, totalBits: 32, signed: false, }); const args: CombatConfigArgs = { globalCeasefire: false, interactionRange: range, lootExclusivityTime: 0n, maxLootRate: lootRate, starbaseUpgradeProgressContinuation: upgradeContinuation, crewRespawnTime: 0n, cargoRespawnTime: 0n, rawShipsRespawnTime: 0n, maxFleetSize: 1, }; const codec = getCombatConfigCodec(); const decoded = codec.decode(codec.encode(args)); expect(decoded.interactionRange.raw).toBe(range.raw); expect(decoded.maxLootRate.raw).toBe(lootRate.raw); expect(decoded.starbaseUpgradeProgressContinuation.raw).toBe( upgradeContinuation.raw, ); }); it('MovementStats encodes fixed-point speeds and fuel rates', () => { const speed = FixedPoint.fromNumber(2.5, { fractionalBits: 16, totalBits: 32, signed: false, }); const distance = FixedPoint.fromNumber(10, { fractionalBits: 8, totalBits: 16, signed: false, }); const fuel = FixedPoint.fromNumber(0.75, { fractionalBits: 6, totalBits: 32, signed: false, }); const args: MovementStatsArgs = { subwarpSpeed: speed, warpSpeed: speed, warpLaneSpeed: speed, maxWarpDistance: distance, warpCoolDown: 0, subwarpFuelConsumptionRate: fuel, warpFuelConsumptionRate: fuel, planetExitFuelAmount: 0, warpXpRate: speed, subwarpXpRate: speed, }; const codec = getMovementStatsCodec(); const decoded = codec.decode(codec.encode(args)); expect(decoded.subwarpSpeed.raw).toBe(speed.raw); expect(decoded.maxWarpDistance.raw).toBe(distance.raw); expect(decoded.subwarpFuelConsumptionRate.raw).toBe(fuel.raw); }); it('CargoStats encodes fixed-point consumption rates', () => { const u18f14 = FixedPoint.fromNumber(1.5, { fractionalBits: 14, totalBits: 32, signed: false, }); const u26f6 = FixedPoint.fromNumber(2.25, { fractionalBits: 6, totalBits: 32, signed: false, }); const args: CargoStatsArgs = { cargoCapacity: 1n, fuelCapacity: 1n, ammoCapacity: 1n, ammoConsumptionRate: u18f14, foodConsumptionRate: u18f14, miningRate: u18f14, loadRate: u26f6, }; const codec = getCargoStatsCodec(); const decoded = codec.decode(codec.encode(args)); expect(decoded.ammoConsumptionRate.raw).toBe(u18f14.raw); expect(decoded.loadRate.raw).toBe(u26f6.raw); }); it('CombatStats encodes fixed-point rates', () => { const u26f6 = FixedPoint.fromNumber(1.1, { fractionalBits: 6, totalBits: 32, signed: false, }); const args = getCombatStatsArgs({ ap: 1, hp: 1, sp: 1, ammoConsumptionRate: u26f6, apRegenRate: u26f6, shieldRechargeRate: u26f6, repairRate: u26f6, repairAbility: u26f6, repairEfficiency: u26f6, repairCost: u26f6, lootRate: u26f6, shieldBreakDelay: 0, warpSpoolTime: 0, }); const codec = getCombatStatsCodec(); const decoded = codec.decode(codec.encode(args)); expect(decoded.lootRate.raw).toBe(u26f6.raw); expect(decoded.repairCost.raw).toBe(u26f6.raw); }); it('ScanCost costMultiplier encodes as U16F16', () => { const u16f16 = FixedPoint.fromNumber(1.5, { fractionalBits: 16, totalBits: 32, signed: false, }); const args: ScanCostArgs = { cargo: 1, costMultiplier: u16f16, }; const codec = getScanCostCodec(); const decoded = codec.decode(codec.encode(args)); expect(decoded.costMultiplier.raw).toBe(u16f16.raw); }); it('ScanNoiseMap encodes I8F56 coordinates and U16F16 multipliers', () => { const u16f16 = FixedPoint.fromNumber(0.25, { fractionalBits: 16, totalBits: 32, signed: false, }); const i8f56Center = FixedPoint.fromNumber(-12.75, { fractionalBits: 56, totalBits: 64, signed: true, }); const i8f56MaxDistance = FixedPoint.fromNumber(24.5, { fractionalBits: 56, totalBits: 64, signed: true, }); const args: ScanNoiseMapArgs = { noiseMapType: 0, noisePowerFunction: 0, xMultiplier: u16f16, yMultiplier: u16f16, zMultiplier: u16f16, xCenter: i8f56Center, yCenter: i8f56Center, originMaxDistance: i8f56MaxDistance, weight: u16f16, seedOffset: 1, }; const codec = getScanNoiseMapCodec(); const decoded = codec.decode(codec.encode(args)); expect(decoded.weight.raw).toBe(u16f16.raw); expect(decoded.xCenter.raw).toBe(i8f56Center.raw); expect(decoded.yCenter.raw).toBe(i8f56Center.raw); expect(decoded.originMaxDistance.raw).toBe(i8f56MaxDistance.raw); }); it('ScanNoiseMapBorsh encodes I8F56 coordinates and U16F16 multipliers', () => { const u16f16 = FixedPoint.fromNumber(0.75, { fractionalBits: 16, totalBits: 32, signed: false, }); const i8f56Center = FixedPoint.fromNumber(-0.5, { fractionalBits: 56, totalBits: 64, signed: true, }); const i8f56MaxDistance = FixedPoint.fromNumber(8.125, { fractionalBits: 56, totalBits: 64, signed: true, }); const args: ScanNoiseMapBorshArgs = { noiseMapType: 0, noisePowerFunction: 0, xMultiplier: u16f16, yMultiplier: u16f16, zMultiplier: u16f16, xCenter: i8f56Center, yCenter: i8f56Center, originMaxDistance: i8f56MaxDistance, weight: u16f16, seedOffset: 1, }; const codec = getScanNoiseMapBorshCodec(); const decoded = codec.decode(codec.encode(args)); expect(decoded.zMultiplier.raw).toBe(u16f16.raw); expect(decoded.xCenter.raw).toBe(i8f56Center.raw); expect(decoded.originMaxDistance.raw).toBe(i8f56MaxDistance.raw); expect(decoded.seedOffset).toBe(1); }); it('ScanLootEntry encodes fixed fields', () => { const u16f16 = FixedPoint.fromNumber(2.0, { fractionalBits: 16, totalBits: 32, signed: false, }); const args: ScanLootEntryArgs = { output: 1, outputAmount: 2n, threshold: u16f16, rollWeight: u16f16, xpValue: u16f16, researchRequirementCount: 0, researchRequirements: [0, 0, 0, 0], }; const codec = getScanLootEntryCodec(); const decoded = codec.decode(codec.encode(args)); expect(decoded.threshold.raw).toBe(u16f16.raw); expect(decoded.xpValue.raw).toBe(u16f16.raw); expect(decoded.researchRequirementCount).toBe(0); expect(decoded.researchRequirements).toEqual([0, 0, 0, 0]); }); it('CreateScanPatternArgs encodes fixed fields', () => { const u16f16 = FixedPoint.fromNumber(1.2, { fractionalBits: 16, totalBits: 32, signed: false, }); const args: CreateScanPatternArgsArgs = { name: 'scan', cooldownMultiplier: u16f16, blankWeight: u16f16, blankXpValue: u16f16, scanCost: [], lootTable: [], noiseMaps: [], }; const codec = getCreateScanPatternArgsCodec(); const decoded = codec.decode(codec.encode(args)); expect(decoded.cooldownMultiplier.raw).toBe(u16f16.raw); expect(decoded.blankXpValue.raw).toBe(u16f16.raw); }); it('UpdateScanPatternArgs encodes optional fixed fields', () => { const u16f16 = FixedPoint.fromNumber(1.875, { fractionalBits: 16, totalBits: 32, signed: false, }); const args: UpdateScanPatternArgsArgs = { name: null, cooldownMultiplier: u16f16, blankWeight: u16f16, blankXpValue: u16f16, scanCost: null, lootTable: null, }; const codec = getUpdateScanPatternArgsCodec(); const decoded = codec.decode(codec.encode(args)); const cooldownMultiplier = unwrapOptionValue(decoded.cooldownMultiplier); const blankWeight = unwrapOptionValue(decoded.blankWeight); const blankXpValue = unwrapOptionValue(decoded.blankXpValue); expect(cooldownMultiplier?.raw).toBe(u16f16.raw); expect(blankWeight?.raw).toBe(u16f16.raw); expect(blankXpValue?.raw).toBe(u16f16.raw); }); it('AsteroidResourceState richness encodes as U16F48', () => { const richness = FixedPoint.fromNumber(4.25, { fractionalBits: 48, totalBits: 64, signed: false, }); const args: AsteroidResourceStateArgs = { richness, amountMined: 0n, miners: 0n, }; const codec = getAsteroidResourceStateCodec(); const decoded = codec.decode(codec.encode(args)); expect(decoded.richness.raw).toBe(richness.raw); }); it('MiscStats round trips numeric fields', () => { const args: MiscStatsArgs = { requiredCrew: 1, passengerCapacity: 2, respawnTimeWithoutFee: 3, respawnTimeWithFee: 4, recoveryFee: 5n, scanCoolDown: 6, scanPower: 7, scanCost: 8, scanBonus: 9, lpValue: 10, medallionValue: 11, xpValue: 11, }; const codec = getMiscStatsCodec(); const decoded = codec.decode(codec.encode(args)); expect(decoded.recoveryFee).toBe(5n); expect(decoded.scanBonus).toBe(9); expect(decoded.medallionValue).toBe(11); expect(decoded.xpValue).toBe(11); }); it('Region xpModifier and border coordinates encode as fixed points', () => { const u16f16 = FixedPoint.fromNumber(1.5, { fractionalBits: 16, totalBits: 32, signed: false, }); const point = FixedPoint.fromNumber(0.5, { fractionalBits: 56, totalBits: 64, signed: true, }); const args = { id: 1, name: 'region', owner: Faction.Mud, safetyStatus: SafetyStatus.Safe, boundingBox: { minX: 0n, maxX: 1n, minY: 0n, maxY: 1n }, xpModifier: u16f16, riskZone: riskZone('LowRiskZone', [Faction.Mud]), border: [ [point, point], [point, point], [point, point], ], neighborsByEdge: [1], connections: [1], systems: new Map([[1, Faction.Mud]]), coreSystems: new Map(), scanningResearchRequirements: [], miningResearchRequirements: [], }; const codec = getRegionCodec(); const decoded = codec.decode(codec.encode(args)); expect(decoded.xpModifier.raw).toBe(u16f16.raw); decoded.border.forEach((coord) => coord.forEach((p) => expect(p.raw).toBe(point.raw)), ); }); it('BoundingBox encodes I8F56 points', () => { const point = FixedPoint.fromNumber(-1.25, { fractionalBits: 56, totalBits: 64, signed: true, }); const codec = getBoundingBoxCodec(); const decoded = codec.decode( codec.encode({ minX: point, maxX: point, minY: point, maxY: point, }), ); expect(decoded.minX.raw).toBe(point.raw); expect(decoded.maxY.raw).toBe(point.raw); }); it('ClaimStakeDefinition multipliers encode as U8F56', () => { const u8f56 = FixedPoint.fromNumber(1.75, { fractionalBits: 56, totalBits: 64, signed: false, }); const args: ClaimStakeDefinitionArgs = { name: 'test', cargoId: 1, tier: 0, slots: 1, hubValue: 0, requiredTags: new Set([1]), providedTags: new Set([1]), rentMultiplier: u8f56, placementFeeMultiplier: u8f56, }; const codec = getClaimStakeDefinitionCodec(); const decoded = codec.decode(codec.encode(args)); expect(decoded.rentMultiplier.raw).toBe(u8f56.raw); expect(decoded.placementFeeMultiplier.raw).toBe(u8f56.raw); }); it('CraftingHabDefinition multipliers encode as U8F56', () => { const u8f56 = FixedPoint.fromNumber(1.5, { fractionalBits: 56, totalBits: 64, signed: false, }); const args: CraftingHabDefinitionArgs = { name: 'hab', cargoId: 2, tier: 1, slots: 2, hubValue: 0, requiredTags: new Set([1]), providedTags: new Set([1]), rentMultiplier: u8f56, placementFeeMultiplier: u8f56, }; const codec = getCraftingHabDefinitionCodec(); const decoded = codec.decode(codec.encode(args)); expect(decoded.rentMultiplier.raw).toBe(u8f56.raw); expect(decoded.placementFeeMultiplier.raw).toBe(u8f56.raw); }); it('BuildingDefinition/HabBuildingDefinition encode fixed points', () => { const xp = FixedPoint.fromNumber(10.25, { fractionalBits: 16, totalBits: 32, signed: false, }); const modifiers = FixedPoint.fromNumber(1.1, { fractionalBits: 6, totalBits: 32, signed: false, }); const buildingArgs = { buildingId: 1, name: 'bld', slots: 1, crewSlots: 1, neededCrew: 1, constructionTime: 0n, power: 0, resourceStorage: 0n, hubValue: 0, minimumTier: 0, xpValue: xp, refunds: new Map(), constructionCost: new Map(), researchRequirements: new Set([1]), netResourceProduction: new Map([[1, 0]]), resourceExtraction: new Map([[1, 0n]]), requiredTags: new Set([1]), providedTags: new Set([1]), }; const habArgs: HabBuildingDefinitionArgs = { buildingId: 2, name: 'hab', slots: 1, crewSlots: 1, neededCrew: 1, constructionTime: 0n, resourceStorage: 0n, hubValue: 0, minimumTier: 0, craftingModifiers: { craftingEfficiencyModifier: modifiers, craftingSpeedModifier: modifiers, crewCountModifier: modifiers, craftingFeeModifier: modifiers, }, craftingJobConcurrency: 1, xpValue: xp, refunds: new Map(), constructionCost: new Map(), researchRequirements: new Set([1]), requiredTags: new Set([1]), providedTags: new Set([1]), netResourceProduction: new Map([[1, 0]]), recipesUnlocked: new Set([1]), }; const building = getBuildingDefinitionCodec().decode( getBuildingDefinitionCodec().encode(buildingArgs), ); const hab = getHabBuildingDefinitionCodec().decode( getHabBuildingDefinitionCodec().encode(habArgs), ); expect(building.xpValue.raw).toBe(xp.raw); expect(hab.xpValue.raw).toBe(xp.raw); expect(hab.craftingModifiers.craftingEfficiencyModifier.raw).toBe( modifiers.raw, ); expect(hab.craftingModifiers.craftingFeeModifier.raw).toBe(modifiers.raw); }); it('CharacterModifier multipliers and rare minerals encode fixed points', () => { const u32f32 = FixedPoint.fromNumber(1.25, { fractionalBits: 32, totalBits: 64, signed: false, }); const i32f32 = FixedPoint.fromNumber(1.25, { fractionalBits: 32, totalBits: 64, signed: true, }); const u16f48 = FixedPoint.fromNumber(1.1, { fractionalBits: 48, totalBits: 64, signed: false, }); const args: CharacterModifierArgs = { fleetSize: 1, fleetConcurrency: 1, respawnTimeModifier: u32f32, warpLaneFeeModifier: u32f32, warpSpeedModifier: i32f32, subwarpSpeedModifier: i32f32, warpCooldownModifier: i32f32, craftingEfficiencyModifier: i32f32, craftingTimeModifier: i32f32, craftingJobConcurrencyBonus: 0, maxResidentialPlotTier: 0, maxResidentialPlots: 0, maxProductivePlotTier: 0, maxProductivePlots: 0, securityZoneAccess: 0, enemyFactionZoneAccess: false, maxDailyStreakLength: 0, maxShipSize: 0, scanPowerModifier: i32f32, scanCoolDownModifier: i32f32, researchTags: new Set([1]), cargoCategories: new Set([1]), rareMineralDiscovery: new Map([[1, u16f48]]), }; const codec = getCharacterModifierCodec(); const decoded = codec.decode(codec.encode(args)); expect(decoded.respawnTimeModifier.raw).toBe(u32f32.raw); expect(decoded.warpLaneFeeModifier.raw).toBe(u32f32.raw); decoded.rareMineralDiscovery.forEach((val) => expect(val.raw).toBe(u16f48.raw), ); }); it('CreateRecipeInputData value and min/max encode as U32F32', () => { const u32f32 = FixedPoint.fromNumber(2.75, { fractionalBits: 32, totalBits: 64, signed: false, }); const args: CreateRecipeInputDataArgs = { recipeId: 1, duration: 1n, minDuration: 1n, name: 'recipe', feeAmount: 0n, feeRecipient: 0, usageLimit: 0n, minStarbaseLevel: 0, value: u32f32, craftingEfficiencyMinmax: { min: u32f32, max: u32f32 }, craftingSpeedMinmax: { min: u32f32, max: u32f32 }, craftingCrewCountMinmax: { min: u32f32, max: u32f32 }, craftingFeeMinmax: { min: u32f32, max: u32f32 }, habRequired: false, researchRequirements: [], inputs: [], outputs: [], }; const codec = getCreateRecipeInputDataCodec(); const decoded = codec.decode(codec.encode(args)); expect(decoded.value.raw).toBe(u32f32.raw); expect(decoded.craftingEfficiencyMinmax.min.raw).toBe(u32f32.raw); expect(decoded.craftingFeeMinmax.max.raw).toBe(u32f32.raw); }); it('UpdateRecipeInputData value and min/max encode as U32F32', () => { const u32f32 = FixedPoint.fromNumber(3.5, { fractionalBits: 32, totalBits: 64, signed: false, }); const args: UpdateRecipeInputDataArgs = { recipeId: null, duration: null, minDuration: null, name: null, feeAmount: null, feeRecipient: null, usageLimit: null, minStarbaseLevel: null, value: u32f32, craftingEfficiencyMinmax: { min: u32f32, max: u32f32 }, craftingSpeedMinmax: { min: u32f32, max: u32f32 }, craftingCrewCountMinmax: { min: u32f32, max: u32f32 }, craftingFeeMinmax: { min: u32f32, max: u32f32 }, habRequired: null, researchRequirements: null, inputs: null, outputs: null, }; const codec = getUpdateRecipeInputDataCodec(); const decoded = codec.decode(codec.encode(args)); expect(decoded.value.raw).toBe(u32f32.raw); expect(decoded.craftingSpeedMinmax.max.raw).toBe(u32f32.raw); }); it('MoveWarp destination encodes I8F56', () => { const coord = FixedPoint.fromNumber(3.125, { fractionalBits: 56, totalBits: 64, signed: true, }); const codec = getMoveWarpCodec(); const decoded = codec.decode( codec.encode({ to: [coord, coord], warpStart: 0n, warpFinish: 1n, }), ); decoded.to.forEach((value) => expect(value.raw).toBe(coord.raw)); }); it('Journey endpoints encode I8F56', () => { const coord = FixedPoint.fromNumber(-0.75, { fractionalBits: 56, totalBits: 64, signed: true, }); const codec = getJourneyCodec(); const decoded = codec.decode( codec.encode({ from: [coord, coord], to: [coord, coord], departureTime: 0n, duration: 0n, }), ); decoded.from.forEach((value) => expect(value.raw).toBe(coord.raw)); decoded.to.forEach((value) => expect(value.raw).toBe(coord.raw)); }); it('UpdateStarSystemArgs coordinates encode I8F56', () => { const coord = FixedPoint.fromNumber(0.875, { fractionalBits: 56, totalBits: 64, signed: true, }); const args: UpdateStarSystemArgsArgs = { coordinates: [coord, coord], name: null, regionId: null, connections: null, }; const codec = getUpdateStarSystemArgsCodec(); const decoded = codec.decode(codec.encode(args)); const coords = unwrapOptionValue(decoded.coordinates) ?? []; coords.forEach((value) => expect(value.raw).toBe(coord.raw)); }); it('Fleet coordinates encode I8F56', () => { const coord = FixedPoint.fromNumber(-1.25, { fractionalBits: 56, totalBits: 64, signed: true, }); const movementStats = { subwarpSpeed: 0, warpSpeed: 0, warpLaneSpeed: 0, maxWarpDistance: 0, warpCoolDown: 0, subwarpFuelConsumptionRate: 0, warpFuelConsumptionRate: 0, planetExitFuelAmount: 0, warpXpRate: 0, subwarpXpRate: 0, }; const cargoStats = { cargoCapacity: 0n, fuelCapacity: 0n, ammoCapacity: 0n, ammoConsumptionRate: 0, foodConsumptionRate: 0, miningRate: 0, loadRate: 0, }; const combatStats = getCombatStatsArgs({ ap: 0, hp: 0, sp: 0, ammoConsumptionRate: 0, apRegenRate: 0, shieldRechargeRate: 0, repairRate: 0, repairAbility: 0, repairEfficiency: 0, repairCost: 0, lootRate: 0, shieldBreakDelay: 0, warpSpoolTime: 0, }); const miscStats = { requiredCrew: 0, passengerCapacity: 0, respawnTimeWithoutFee: 0, respawnTimeWithFee: 0, recoveryFee: 0n, scanCoolDown: 0, scanPower: 0, scanCost: 0, scanBonus: 0, lpValue: 0, medallionValue: 0, xpValue: 0, }; const args: FleetArgs = { version: 0, bump: 0, gameId: '11111111111111111111111111111111' as Address, ownerProfile: '11111111111111111111111111111111' as Address, subProfile: '11111111111111111111111111111111' as Address, subProfileInvalidator: '11111111111111111111111111111111' as Address, faction: Faction.Mud, npcFactionId: 0, fleetLabel: 'fleet', shipCounts: { total: 0, xxSmall: 0, xSmall: 0, small: 0, medium: 0, large: 0, capital: 0, commander: 0, titan: 0, }, warpCooldownExpiresAt: 0n, scanCooldownExpiresAt: 0n, stats: { movementStats, cargoStats, combatStats, miscStats, }, fuelTank: { cargoId: 0, amount: 0n }, ammoBank: { cargoId: 0, amount: 0n }, crewCount: 0, rentedCrew: 0, shipDefinitionsSeqId: 0, ap: 0, sp: 0, hp: 0, pendingHp: 0, stance: { ammoUsage: 0 }, apReloadExpiresAt: 0n, shieldBreakDelayExpiresAt: 0n, lastCombatUpdate: 0n, location: [coord, coord], lastUpdateAt: 0n, fleetShips: new Map(), respawningCargo: { seqId: 0, storageCost: 0n, amounts: new Map() }, components: { seqId: 0, storageCost: 0n, amounts: new Map() }, cargoHold: { seqId: 0, storageCost: 0n, amounts: new Map() }, activeStimulants: new Map(), state: { __kind: 'Idle', fields: [[]] }, }; const codec = getFleetAccountCodec(); const decoded = codec.decode(codec.encode(args)); decoded.location.forEach((value) => expect(value.raw).toBe(coord.raw)); }); it('StarSystem coordinates encode I8F56', () => { const coord = FixedPoint.fromNumber(2.5, { fractionalBits: 56, totalBits: 64, signed: true, }); const args: StarSystemArgs = { version: 0, gameId: '11111111111111111111111111111111' as Address, systemId: 1, name: 'system', region: null, coordinates: [coord, coord], seqId: 0, connections: new Map(), celestialBodies: new Set(), starbase: null, }; const codec = getStarSystemAccountCodec(); const decoded = codec.decode(codec.encode(args)); decoded.coordinates.forEach((value) => expect(value.raw).toBe(coord.raw)); }); it('Loot coordinates encode I8F56', () => { const coord = FixedPoint.fromNumber(0.5, { fractionalBits: 56, totalBits: 64, signed: true, }); const args: LootArgs = { version: 0, gameId: '11111111111111111111111111111111' as Address, profile: '11111111111111111111111111111111' as Address, coordinates: [coord, coord], rewardStatus: 0, rewardLegCount: 0, rewardPad: [0, 0], rewardConfigVersion: 0, rewardCommitSlot: 0n, rewardCommittedAt: 0n, rewardLegs: [ { recipient: '11111111111111111111111111111111' as Address, medallions: 0n, salvageTargetValue: 0n, }, { recipient: '11111111111111111111111111111111' as Address, medallions: 0n, salvageTargetValue: 0n, }, ], items: { unsizedLen: 0, offsetList: [], unsizedList: [] }, }; const codec = getLootCodec(); const decoded = codec.decode(codec.encode(args)); decoded.coordinates.forEach((value) => expect(value.raw).toBe(coord.raw)); }); it('ScanPattern account encodes fixed fields and status', () => { const u16f16 = FixedPoint.fromNumber(1.125, { fractionalBits: 16, totalBits: 32, signed: false, }); const args: ScanPatternArgs = { version: 0, bump: 0, scanPatternId: 1, name: 'scan-pattern', gameId: '11111111111111111111111111111111' as Address, cooldownMultiplier: u16f16, blankWeight: u16f16, blankXpValue: u16f16, status: ScanPatternStatus.Active, cost: [], lootTable: [], noiseMaps: [], }; const codec = getScanPatternAccountCodec(); const decoded = codec.decode(codec.encode(args)); expect(decoded.cooldownMultiplier.raw).toBe(u16f16.raw); expect(decoded.blankWeight.raw).toBe(u16f16.raw); expect(decoded.blankXpValue.raw).toBe(u16f16.raw); expect(decoded.status).toBe(ScanPatternStatus.Active); }); it('MinMax uses U32F32 bounds', () => { const u32f32 = FixedPoint.fromNumber(1.125, { fractionalBits: 32, totalBits: 64, signed: false, }); const codec = getMinMaxCodec(); const decoded = codec.decode(codec.encode({ min: u32f32, max: u32f32 })); expect(decoded.min.raw).toBe(u32f32.raw); expect(decoded.max.raw).toBe(u32f32.raw); }); it('MineAsteroid regionXpModifier uses U16F16', () => { const u16f16 = FixedPoint.fromNumber(1.25, { fractionalBits: 16, totalBits: 32, signed: false, }); const asteroid: Address = '11111111111111111111111111111111' as Address; const args = { asteroid, regionXpModifier: u16f16, resources: [1], }; const codec = getMineAsteroidCodec(); const decoded = codec.decode(codec.encode(args)); expect(decoded.regionXpModifier.raw).toBe(u16f16.raw); }); });