addBonusToTotalIfNecessary = (bonusDef, gearInfo) => {
			// Conditional filters, combinations are logic AND, all filters existed have to be passed
			if(Array.isArray(bonusDef.ids) && !bonusDef.ids.includes(shipMasterId)) { return; }
			if(Array.isArray(bonusDef.excludes) && bonusDef.excludes.includes(shipMasterId)) { return; }
			if(Array.isArray(bonusDef.origins) && !bonusDef.origins.includes(shipOriginId)) { return; }
			if(Array.isArray(bonusDef.excludeOrigins) && bonusDef.excludeOrigins.includes(shipOriginId)) { return; }
			if(Array.isArray(bonusDef.classes) && !bonusDef.classes.includes(shipClassId)) { return; }
			if(Array.isArray(bonusDef.excludeClasses) && bonusDef.excludeClasses.includes(shipClassId)) { return; }
			if(Array.isArray(bonusDef.stypes) && !bonusDef.stypes.includes(shipTypeId)) { return; }
			if(Array.isArray(bonusDef.excludeStypes) && bonusDef.excludeStypes.includes(shipTypeId)) { return; }
			if(Array.isArray(bonusDef.distinctGears)) {
				const flagsKey = "countOnceIds" + bonusDef.distinctGears.join("_");
				synergyGears[flagsKey] = (synergyGears[flagsKey] || 0) + 1;
				if(synergyGears[flagsKey] > 1) { return; }
			}
			if(bonusDef.remodel || bonusDef.remodelCap) {
				const remodelGroup = RemodelDb.remodelGroup(shipMasterId);
				if(remodelGroup.indexOf(shipMasterId) < bonusDef.remodel) { return; }
				if(remodelGroup.indexOf(shipMasterId) > bonusDef.remodelCap) { return; }
			}
			let gearCount = gearInfo.count;
			if(bonusDef.minStars && gearInfo.starsDist) {
				gearCount = gearInfo.starsDist.slice(bonusDef.minStars).sumValues();
				if(!gearCount) { return; }
			}
			if(bonusDef.minCount) {
				let reqMinCount = gearCount;
				if(Array.isArray(bonusDef.distinctGears)) {
					reqMinCount = bonusDef.distinctGears.reduce(
						(acc, id) => (acc + (bonusGears[id] || {}).count || 0), 0
					);
				}
				if(reqMinCount < bonusDef.minCount) { return; }
			}
			// Additive bonus actions
			if(bonusDef.single) { total += bonusDef.single[apiName] || 0; }
			if(bonusDef.multiple) {
				total += (bonusDef.multiple[apiName] || 0) *
					(bonusDef.countCap ? Math.min(bonusDef.countCap, gearCount) : gearCount);
			}
			if(bonusDef.synergy) {
				const addBonusFromSynergyGears = (synergy) => {
					// All flags are true (logic AND, no logic OR/NOT yet)
					if(synergy.flags.every(flag => synergyGears[flag] > 0)) {
						if(synergy.single) { total += synergy.single[apiName] || 0; }
						if(synergy.multiple) {
							const countAmount = synergy.countFlag === undefined ? gearCount :
								(synergyGears[synergy.flags[synergy.countFlag]] || 0);
							total += (synergy.multiple[apiName] || 0) *
								(synergy.countCap ? Math.min(synergy.countCap, countAmount) : countAmount);
						}
						if(synergy.distinct) {
							const flagsKey = synergy.flags.join("_") + "Applied";
							synergyGears[flagsKey] = (synergyGears[flagsKey] || 0) + 1;
							if(synergyGears[flagsKey] < 2) { total += synergy.distinct[apiName] || 0; }
						}
						if(synergy.byCount) {
							const gearName = synergy.byCount.gear;
							const countAmount = gearName === "this" ? gearCount : synergyGears[gearName] || 0;
							if(synergy.byCount.distinct) {
								const flagsKey = synergy.flags.join("_") + "Applied";
								synergyGears[flagsKey] = (synergyGears[flagsKey] || 0) + 1;
								if(synergyGears[flagsKey] < 2) {
									total += (synergy.byCount[countAmount] || {})[apiName] || 0;
								}
							} else {
								total += (synergy.byCount[countAmount] || {})[apiName] || 0;
							}
						}
						if(synergy.byStars) {
							const gearMstId = synergy.byStars.gearId;
							const starDist = (bonusGears[gearMstId] || {}).starsDist || [];
							const isMultiple = !!synergy.byStars.multiple;
							const countCap = synergy.byStars.countCap || Number.POSITIVE_INFINITY;
							const lessStars = synergy.byStars.noStarsLessThan;
							if(!lessStars || starDist.slice(0, lessStars).sumValues() === 0) {
								Object.keys(synergy.byStars).forEach(starStr => {
									const minStars = parseInt(starStr);
									if(!isNaN(minStars)) {
										const synergyGearCount = starDist.slice(minStars).sumValues();
										if(synergyGearCount > 0) {
											total += ((synergy.byStars[starStr] || {})[apiName] || 0) * Math.min(countCap, isMultiple ? synergyGearCount : 1);
										}
									}
								});
							}
						}
					}
				};
				if(Array.isArray(bonusDef.synergy)) {
					bonusDef.synergy.forEach(addBonusFromSynergyGears);
				} else {
					addBonusFromSynergyGears(bonusDef.synergy);
				}
			}
			// Limit speed here only affect this bonus item, multiple speed bonuses could over cap
			if(bonusDef.speedCap && apiName === "soku") {
				total = Math.min(bonusDef.speedCap, total);
			}
			// Try not to use any callback in order to let bonus table suit for a JSON
			//if(bonusDef.callback) { total += bonusDef.callback(apiName, gearInfo, synergyGears); }
		}
@param bonusGears - the accmulated bonus definition table.
@param {Object} ship - the ship object instance with bonus equipment.
@param {string} apiName - the api name of gear stat given to the ship.
@return {number} ship's bonus of specified stat (by api name) based on accmulated bonus definition table.
@see KC3Ship.prototype.equipmentTotalStats