ffxiv-teamcraft/simulator

Bug: Version 1.1+ appears to break progression and quality increases

karashiiro opened this issue · 6 comments

Tried executing a Hempen Yarn craft on the latest version (1.2.0) and on 1.0.5. The following is the returned object from simulation.run(). The same code and the same parameters were used in both runs, which is to say that the version was controlled for. Notably, on versions 1.1.0 and above, this craft with a 1x Focused Touch -> 2x Focused Synthesis macro returns NaN at certain points, failing the craft with an undefined fail cause. I haven't formally tested this bug on other crafts, but I did notice it return NaN values with this same macro on the Rakshasa Ring of Aiming on version 1.1.0.

Latest version:

3|prima-extra     | { steps:
3|prima-extra     |    [ { action: Observe {},
3|prima-extra     |        success: true,
3|prima-extra     |        addedQuality: 0,
3|prima-extra     |        addedProgression: 0,
3|prima-extra     |        cpDifference: -7,
3|prima-extra     |        skipped: false,
3|prima-extra     |        solidityDifference: 0,
3|prima-extra     |        state: 'NORMAL',
3|prima-extra     |        failCause: undefined,
3|prima-extra     |        afterBuffTick: [Object] },
3|prima-extra     |      { action: FocusedTouch {},
3|prima-extra     |        success: true,
3|prima-extra     |        addedQuality: NaN,
3|prima-extra     |        addedProgression: 0,
3|prima-extra     |        cpDifference: -18,
3|prima-extra     |        skipped: false,
3|prima-extra     |        solidityDifference: -10,
3|prima-extra     |        state: 'NORMAL',
3|prima-extra     |        failCause: undefined,
3|prima-extra     |        afterBuffTick: [Object] },
3|prima-extra     |      { action: Observe {},
3|prima-extra     |        success: true,
3|prima-extra     |        addedQuality: NaN,
3|prima-extra     |        addedProgression: 0,
3|prima-extra     |        cpDifference: -7,
3|prima-extra     |        skipped: false,
3|prima-extra     |        solidityDifference: 0,
3|prima-extra     |        state: 'NORMAL',
3|prima-extra     |        failCause: undefined,
3|prima-extra     |        afterBuffTick: [Object] },
3|prima-extra     |      { action: FocusedSynthesis {},
3|prima-extra     |        success: true,
3|prima-extra     |        addedQuality: NaN,
3|prima-extra     |        addedProgression: NaN,
3|prima-extra     |        cpDifference: -5,
3|prima-extra     |        skipped: false,
3|prima-extra     |        solidityDifference: -10,
3|prima-extra     |        state: 'NORMAL',
3|prima-extra     |        failCause: undefined,
3|prima-extra     |        afterBuffTick: [Object] },
3|prima-extra     |      { action: Observe {},
3|prima-extra     |        success: true,
3|prima-extra     |        addedQuality: NaN,
3|prima-extra     |        addedProgression: NaN,
3|prima-extra     |        cpDifference: -7,
3|prima-extra     |        skipped: false,
3|prima-extra     |        solidityDifference: 0,
3|prima-extra     |        state: 'NORMAL',
3|prima-extra     |        failCause: undefined,
3|prima-extra     |        afterBuffTick: [Object] },
3|prima-extra     |      { action: FocusedSynthesis {},
3|prima-extra     |        success: true,
3|prima-extra     |        addedQuality: NaN,
3|prima-extra     |        addedProgression: NaN,
3|prima-extra     |        cpDifference: -5,
3|prima-extra     |        skipped: false,
3|prima-extra     |        solidityDifference: -10,
3|prima-extra     |        state: 'NORMAL',
3|prima-extra     |        failCause: undefined,
3|prima-extra     |        afterBuffTick: [Object] } ],
3|prima-extra     |   hqPercent: undefined,
3|prima-extra     |   success: false,
3|prima-extra     |   simulation:
3|prima-extra     |    Simulation {
3|prima-extra     |      recipe:
3|prima-extra     |       { id: 464,
3|prima-extra     |         job: 13,
3|prima-extra     |         rlvl: 1,
3|prima-extra     |         durability: 60,
3|prima-extra     |         quality: 312,
3|prima-extra     |         progress: 9,
3|prima-extra     |         lvl: 1,
3|prima-extra     |         stars: 0,
3|prima-extra     |         hq: 0,
3|prima-extra     |         quickSynth: 0,
3|prima-extra     |         controlReq: 0,
3|prima-extra     |         craftmanshipReq: 0,
3|prima-extra     |         ingredients: [Array],
3|prima-extra     |         yield: 1 },
3|prima-extra     |      actions:
3|prima-extra     |       [ Observe {},
3|prima-extra     |         FocusedTouch {},
3|prima-extra     |         Observe {},
3|prima-extra     |         FocusedSynthesis {},
3|prima-extra     |         Observe {},
3|prima-extra     |         FocusedSynthesis {} ],
3|prima-extra     |      _crafterStats:
3|prima-extra     |       CrafterStats {
3|prima-extra     |         jobId: 13,
3|prima-extra     |         craftsmanship: 1500,
3|prima-extra     |         _control: 1536,
3|prima-extra     |         cp: 539,
3|prima-extra     |         specialist: true,
3|prima-extra     |         level: 69,
3|prima-extra     |         levels: [Array] },
3|prima-extra     |      hqIngredients: [],
3|prima-extra     |      forceFailed: [],
3|prima-extra     |      progression: NaN,
3|prima-extra     |      quality: NaN,
3|prima-extra     |      startingQuality: 0,
3|prima-extra     |      state: 'NORMAL',
3|prima-extra     |      buffs: [],
3|prima-extra     |      success: undefined,
3|prima-extra     |      steps:
3|prima-extra     |       [ [Object], [Object], [Object], [Object], [Object], [Object] ],
3|prima-extra     |      lastPossibleReclaimStep: -1,
3|prima-extra     |      safe: false,
3|prima-extra     |      durability: 30,
3|prima-extra     |      availableCP: 490,
3|prima-extra     |      maxCP: 539 } }

1.0.5:

3|prima-extra     | { steps:
3|prima-extra     |    [ { action: Observe {},
3|prima-extra     |        success: true,
3|prima-extra     |        addedQuality: 0,
3|prima-extra     |        addedProgression: 0,
3|prima-extra     |        cpDifference: -7,
3|prima-extra     |        skipped: false,
3|prima-extra     |        solidityDifference: 0,
3|prima-extra     |        state: 'NORMAL',
3|prima-extra     |        failCause: undefined,
3|prima-extra     |        afterBuffTick: [Object] },
3|prima-extra     |      { action: FocusedTouch {},
3|prima-extra     |        success: true,
3|prima-extra     |        addedQuality: 988,
3|prima-extra     |        addedProgression: 0,
3|prima-extra     |        cpDifference: -18,
3|prima-extra     |        skipped: false,
3|prima-extra     |        solidityDifference: -10,
3|prima-extra     |        state: 'NORMAL',
3|prima-extra     |        failCause: undefined,
3|prima-extra     |        afterBuffTick: [Object] },
3|prima-extra     |      { action: Observe {},
3|prima-extra     |        success: true,
3|prima-extra     |        addedQuality: 0,
3|prima-extra     |        addedProgression: 0,
3|prima-extra     |        cpDifference: -7,
3|prima-extra     |        skipped: false,
3|prima-extra     |        solidityDifference: 0,
3|prima-extra     |        state: 'NORMAL',
3|prima-extra     |        failCause: undefined,
3|prima-extra     |        afterBuffTick: [Object] },
3|prima-extra     |      { action: FocusedSynthesis {},
3|prima-extra     |        success: true,
3|prima-extra     |        addedQuality: 0,
3|prima-extra     |        addedProgression: 1090,
3|prima-extra     |        cpDifference: -5,
3|prima-extra     |        skipped: false,
3|prima-extra     |        solidityDifference: -10,
3|prima-extra     |        state: 'NORMAL',
3|prima-extra     |        failCause: undefined,
3|prima-extra     |        afterBuffTick: [Object] },
3|prima-extra     |      { action: Observe {},
3|prima-extra     |        success: null,
3|prima-extra     |        addedQuality: 0,
3|prima-extra     |        addedProgression: 0,
3|prima-extra     |        cpDifference: 0,
3|prima-extra     |        skipped: true,
3|prima-extra     |        solidityDifference: 0,
3|prima-extra     |        state: 'NORMAL',
3|prima-extra     |        failCause: undefined,
3|prima-extra     |        afterBuffTick: [Object] },
3|prima-extra     |      { action: FocusedSynthesis {},
3|prima-extra     |        success: null,
3|prima-extra     |        addedQuality: 0,
3|prima-extra     |        addedProgression: 0,
3|prima-extra     |        cpDifference: 0,
3|prima-extra     |        skipped: true,
3|prima-extra     |        solidityDifference: 0,
3|prima-extra     |        state: 'GOOD',
3|prima-extra     |        failCause: undefined,
3|prima-extra     |        afterBuffTick: [Object] } ],
3|prima-extra     |   hqPercent: 100,
3|prima-extra     |   success: true,
3|prima-extra     |   simulation:
3|prima-extra     |    Simulation {
3|prima-extra     |      recipe:
3|prima-extra     |       { id: 464,
3|prima-extra     |         job: 13,
3|prima-extra     |         rlvl: 1,
3|prima-extra     |         durability: 60,
3|prima-extra     |         quality: 312,
3|prima-extra     |         progress: 9,
3|prima-extra     |         lvl: 1,
3|prima-extra     |         stars: 0,
3|prima-extra     |         hq: 0,
3|prima-extra     |         quickSynth: 0,
3|prima-extra     |         controlReq: 0,
3|prima-extra     |         craftmanshipReq: 0,
3|prima-extra     |         ingredients: [Array],
3|prima-extra     |         yield: 1 },
3|prima-extra     |      actions:
3|prima-extra     |       [ Observe {},
3|prima-extra     |         FocusedTouch {},
3|prima-extra     |         Observe {},
3|prima-extra     |         FocusedSynthesis {},
3|prima-extra     |         Observe {},
3|prima-extra     |         FocusedSynthesis {} ],
3|prima-extra     |      _crafterStats:
3|prima-extra     |       CrafterStats {
3|prima-extra     |         jobId: 13,
3|prima-extra     |         craftsmanship: 1500,
3|prima-extra     |         _control: 1536,
3|prima-extra     |         cp: 539,
3|prima-extra     |         specialist: true,
3|prima-extra     |         level: 69,
3|prima-extra     |         levels: [Array] },
3|prima-extra     |      hqIngredients: [],
3|prima-extra     |      forceFailed: [],
3|prima-extra     |      progression: 1090,
3|prima-extra     |      quality: 988,
3|prima-extra     |      startingQuality: 0,
3|prima-extra     |      state: 'NORMAL',
3|prima-extra     |      buffs: [],
3|prima-extra     |      success: true,
3|prima-extra     |      steps:
3|prima-extra     |       [ [Object], [Object], [Object], [Object], [Object], [Object] ],
3|prima-extra     |      lastPossibleReclaimStep: -1,
3|prima-extra     |      safe: false,
3|prima-extra     |      durability: 40,
3|prima-extra     |      availableCP: 502,
3|prima-extra     |      maxCP: 539 } }

Can you reproduce this using 1.2.1? If you do, the perfect next step would be to implement a test able to see this bug in order to fix this using TDD.

Sorry for the delay, was waiting on XIVAPI's DB search to be repaired. The results with 1.2.1 seem identical to the bugged output in 1.1.0+.

3|prima-extra  | { steps:
3|prima-extra  |    [ { action: Observe {},
3|prima-extra  |        success: true,
3|prima-extra  |        addedQuality: 0,
3|prima-extra  |        addedProgression: 0,
3|prima-extra  |        cpDifference: -7,
3|prima-extra  |        skipped: false,
3|prima-extra  |        solidityDifference: 0,
3|prima-extra  |        state: 'NORMAL',
3|prima-extra  |        failCause: undefined,
3|prima-extra  |        afterBuffTick: [Object] },
3|prima-extra  |      { action: FocusedTouch {},
3|prima-extra  |        success: true,
3|prima-extra  |        addedQuality: NaN,
3|prima-extra  |        addedProgression: 0,
3|prima-extra  |        cpDifference: -18,
3|prima-extra  |        skipped: false,
3|prima-extra  |        solidityDifference: -10,
3|prima-extra  |        state: 'GOOD',
3|prima-extra  |        failCause: undefined,
3|prima-extra  |        afterBuffTick: [Object] },
3|prima-extra  |      { action: Observe {},
3|prima-extra  |        success: true,
3|prima-extra  |        addedQuality: NaN,
3|prima-extra  |        addedProgression: 0,
3|prima-extra  |        cpDifference: -7,
3|prima-extra  |        skipped: false,
3|prima-extra  |        solidityDifference: 0,
3|prima-extra  |        state: 'GOOD',
3|prima-extra  |        failCause: undefined,
3|prima-extra  |        afterBuffTick: [Object] },
3|prima-extra  |      { action: FocusedSynthesis {},
3|prima-extra  |        success: true,
3|prima-extra  |        addedQuality: NaN,
3|prima-extra  |        addedProgression: NaN,
3|prima-extra  |        cpDifference: -5,
3|prima-extra  |        skipped: false,
3|prima-extra  |        solidityDifference: -10,
3|prima-extra  |        state: 'NORMAL',
3|prima-extra  |        failCause: undefined,
3|prima-extra  |        afterBuffTick: [Object] },
3|prima-extra  |      { action: Observe {},
3|prima-extra  |        success: true,
3|prima-extra  |        addedQuality: NaN,
3|prima-extra  |        addedProgression: NaN,
3|prima-extra  |        cpDifference: -7,
3|prima-extra  |        skipped: false,
3|prima-extra  |        solidityDifference: 0,
3|prima-extra  |        state: 'NORMAL',
3|prima-extra  |        failCause: undefined,
3|prima-extra  |        afterBuffTick: [Object] },
3|prima-extra  |      { action: FocusedSynthesis {},
3|prima-extra  |        success: true,
3|prima-extra  |        addedQuality: NaN,
3|prima-extra  |        addedProgression: NaN,
3|prima-extra  |        cpDifference: -5,
3|prima-extra  |        skipped: false,
3|prima-extra  |        solidityDifference: -10,
3|prima-extra  |        state: 'GOOD',
3|prima-extra  |        failCause: undefined,
3|prima-extra  |        afterBuffTick: [Object] } ],
3|prima-extra  |   hqPercent: undefined,
3|prima-extra  |   success: false,
3|prima-extra  |   simulation:
3|prima-extra  |    Simulation {
3|prima-extra  |      recipe:
3|prima-extra  |       { id: 464,
3|prima-extra  |         job: 13,
3|prima-extra  |         rlvl: 1,
3|prima-extra  |         durability: 60,
3|prima-extra  |         quality: 312,
3|prima-extra  |         progress: 9,
3|prima-extra  |         lvl: 1,
3|prima-extra  |         stars: 0,
3|prima-extra  |         hq: 0,
3|prima-extra  |         quickSynth: 0,
3|prima-extra  |         controlReq: 0,
3|prima-extra  |         craftmanshipReq: 0,
3|prima-extra  |         ingredients: [Array],
3|prima-extra  |         yield: 1 },
3|prima-extra  |      actions:
3|prima-extra  |       [ Observe {},
3|prima-extra  |         FocusedTouch {},
3|prima-extra  |         Observe {},
3|prima-extra  |         FocusedSynthesis {},
3|prima-extra  |         Observe {},
3|prima-extra  |         FocusedSynthesis {} ],
3|prima-extra  |      _crafterStats:
3|prima-extra  |       CrafterStats {
3|prima-extra  |         jobId: 13,
3|prima-extra  |         craftsmanship: 1500,
3|prima-extra  |         _control: 1536,
3|prima-extra  |         cp: 539,
3|prima-extra  |         specialist: true,
3|prima-extra  |         level: 69,
3|prima-extra  |         levels: [Array] },
3|prima-extra  |      hqIngredients: [],
3|prima-extra  |      forceFailed: [],
3|prima-extra  |      progression: NaN,
3|prima-extra  |      quality: NaN,
3|prima-extra  |      startingQuality: 0,
3|prima-extra  |      state: 'NORMAL',
3|prima-extra  |      buffs: [],
3|prima-extra  |      success: undefined,
3|prima-extra  |      steps:
3|prima-extra  |       [ [Object], [Object], [Object], [Object], [Object], [Object] ],
3|prima-extra  |      lastPossibleReclaimStep: -1,
3|prima-extra  |      safe: false,
3|prima-extra  |      durability: 30,
3|prima-extra  |      availableCP: 490,
3|prima-extra  |      maxCP: 539 } }

Can you show me the exact call you're making? Seems like a simple test can't reproduce this, as you can see from the commit above

Removed the JSON processing for clarity, everything in recipe can be retrieved from here.

// Lines 32-40
const jobIdMap = new Map();
jobIdMap.set("CRP", 8);
jobIdMap.set("BSM", 9);
jobIdMap.set("ARM", 10);
jobIdMap.set("GSM", 11);
jobIdMap.set("LTW", 12);
jobIdMap.set("WVR", 13);
jobIdMap.set("ALC", 14);
jobIdMap.set("CUL", 15);
// Line 181
const craftingMacro = Teamcraft.CraftingActionsRegistry.deserializeRotation(craftStats.macro);
// Lines 230-291
var craft = { // "recipe" is a JSON response from https://xivapi.com/recipe/<ID>
	"id": recipe.ID,
	"job": recipe.ClassJob.ID,
	"rlvl": recipe.RecipeLevelTable.ID,
	"durability": recipe.RecipeLevelTable.Durability,
	"quality": recipe.RecipeLevelTable.Quality,
	"progress": Math.floor(recipe.RecipeLevelTable.Difficulty / 2),
	"lvl": recipe.RecipeLevelTable.ClassJobLevel,
	"stars": recipe.RecipeLevelTable.Stars,
	"hq": hq, // 1 or 0
	"quickSynth": quickSynth, // 1 or 0
	"controlReq": recipe.RequiredControl,
	"craftmanshipReq": recipe.RequiredCraftsmanship,
	"ingredients": [],
	"yield": recipe.AmountResult
};

for (var i = 0; i <= 9; i++) {
	if (recipe[`AmountIngredient${i}`] > 0) {
		craft.ingredients.push({
			"id": recipe[`ItemIngredient${i}ID`],
			"amount": recipe[`AmountIngredient${i}`]
		});
	}
}

// Set stats
const STATS = new Teamcraft.CrafterStats(
	jobIdMap.get(userJob),					// User job (int)
	craftStats.craftmanship,				// Craftsmanship (int)
	craftStats.control,						// Control (int)
	craftStats.cp,							// CP (int)
	craftStats.specialist,					// Specialist (true/false)
	jobLevelMap.get(jobIdMap.get(userJob)),	// User job level (int)
	[
		jobLevelMap.get(jobIdMap.get("CRP")), // int
		jobLevelMap.get(jobIdMap.get("BSM")), // int
		jobLevelMap.get(jobIdMap.get("ARM")), // int
		jobLevelMap.get(jobIdMap.get("GSM")), // int
		jobLevelMap.get(jobIdMap.get("LTW")), // int
		jobLevelMap.get(jobIdMap.get("WVR")), // int
		jobLevelMap.get(jobIdMap.get("ALC")), // int
		jobLevelMap.get(jobIdMap.get("CUL")), // int
	]
);

// Execute craft
const simulation = new Teamcraft.Simulation(craft, craftingMacro, STATS);
const result = simulation.run();
const failCause = (reason) => {
	if (reason === "DURABILITY_REACHED_ZERO") return " because the craft's durability hit zero.";
	if (reason === "MISSING_LEVEL_REQUIREMENT") return " because you aren't a high enough level to attempt that craft.";
	if (reason === "NOT_ENOUGH_CP") return " because you don't have enough CP to execute that macro.";
	if (reason === "NOT_SPECIALIST") return " because you can't use Specialist actions without being a Specialist.";
	if (reason === "NO_INNER_QUIET") return " because you didn't execute Inner Quiet when it was required.";
	return ".";
};
console.log(result);
const reliabilityReport = simulation.getReliabilityReport();

logger.log('info', `Result: ${result.success ? "Success!" : "The craft failed" + failCause(result.failCause) + ","} Quality: ${result.hqPercent}%`);

Ok I found the issue (finally).

This is because your recipe doesn't provide a suggestedCraftsmanship or it doesn't provide a suggestedControl (in this case, both).

Oh, it was 3e2a778, adding those properties fixed it.
Thank you for the clarification, closing the issue.