Utilisation et contraintes

Décrivez une recette par capacité, entrées exactes, sorties garanties, durée et FE par tick actif. Incluez chaque sortie secondaire. Le resolver d’un template doit être pur et borné ; un rendement aléatoire moyen n’est pas un résultat garanti.

  1. DécouvrirLire capacités et recettes
  2. PlanifierRéserver les ressources exactes
  3. ExécuterTransférer au vrai propriétaire
  4. ReprendreLire le reçu sauvegardé
La découverte ne modifie rien. Seule l’exécution transfère la propriété ; la reprise ne recrée jamais une entrée consommée.

ProductionModels

ProductionModels
package dev.nebulastorage.api.production

import dev.nebulastorage.api.resource.ResourceStack
import dev.nebulastorage.api.resource.ResourceKey
import dev.nebulastorage.api.resource.quantities
import java.util.Collections
import java.util.UUID

data class ProductionCapabilityId(val value: String) {
    init { require(Regex("[a-z0-9_.-]+:[a-z0-9/._-]+").matches(value)) }
}

data class ProductionRecipeId(val value: String) {
    init { require(Regex("[a-z0-9_.-]+:[a-z0-9/._-]+").matches(value)) }
}

/** Inputs and every secondary output count exact integral units per operation. Energy is FE per paid processing tick. */
class ProductionRecipe(
    override val id: ProductionRecipeId, override val capability: ProductionCapabilityId,
    inputs: List<ResourceStack>, outputs: List<ResourceStack>, override val durationTicks: Int = 1,
    override val priority: Int = 0, override val energyPerTick: Long = 0, override val materialYield: ProductionYield? = null
) : ProductionRecipeOffer {
    val inputs = quantities(inputs)
    override val outputs = quantities(outputs)
    init {
        require(inputs.isNotEmpty() && outputs.isNotEmpty() && durationTicks > 0 && energyPerTick >= 0)
        Math.multiplyExact(energyPerTick, durationTicks.toLong())
    }
}

sealed interface ProductionRecipeOffer {
    val id: ProductionRecipeId
    val capability: ProductionCapabilityId
    val outputs: Map<ResourceKey, Long>
    val durationTicks: Int
    val priority: Int
    val energyPerTick: Long
    val materialYield: ProductionYield?
}

/** Higher ranks mean better material yield only inside the same integration-defined comparison group. */
data class ProductionYield(val group: String, val rank: Long) {
    init { ProductionRecipeId(group); require(rank >= 0) }
}

/** One ingredient position, with exact interchangeable identities. Options are finite and never mutated by planning. */
class ProductionIngredient(options: Set<ResourceKey>, val amount: Long = 1) {
    val options: Set<ResourceKey> = Collections.unmodifiableSet(options.toSet())
    init { require(options.isNotEmpty() && amount > 0) }
}

/** Pure server-thread calculation, including every remainder. Null rejects this particular ingredient selection. */
fun interface RecipeOutputResolver {
    fun resolve(selected: List<ResourceKey>): List<ResourceStack>?
}

/** Indexed outputs are guaranteed per operation. Resolution may add secondary outputs, but cannot change the selected inputs. */
class ProductionRecipeTemplate(
    override val id: ProductionRecipeId, override val capability: ProductionCapabilityId,
    ingredients: List<ProductionIngredient>, outputs: List<ResourceStack>, override val durationTicks: Int = 1,
    override val priority: Int = 0, override val energyPerTick: Long = 0,
    private val resolver: RecipeOutputResolver? = null, override val materialYield: ProductionYield? = null
) : ProductionRecipeOffer {
    val ingredients: List<ProductionIngredient> = Collections.unmodifiableList(ingredients.toList())
    override val outputs = quantities(outputs)
    init {
        require(ingredients.isNotEmpty() && outputs.isNotEmpty() && durationTicks > 0 && energyPerTick >= 0)
        Math.multiplyExact(energyPerTick, durationTicks.toLong())
    }

    fun resolve(selected: List<ResourceKey>): ProductionRecipe? {
        require(selected.size == ingredients.size && selected.indices.all { selected[it] in ingredients[it].options })
        val result = if (resolver == null) outputs.map { (key, amount) -> ResourceStack(key, amount) }
            else resolver.resolve(Collections.unmodifiableList(selected.toList())) ?: return null
        val concrete = ProductionRecipe(id, capability, selected.mapIndexed { index, key -> ResourceStack(key, ingredients[index].amount) },
            result, durationTicks, priority, energyPerTick, materialYield)
        require(outputs.all { (key, amount) -> (concrete.outputs[key] ?: 0) >= amount }) { "Recipe resolution removed an indexed output" }
        return concrete
    }
}

enum class ProductionExecution { CONTROLLED, PHYSICAL }

/** [id] identifies one machine owner. An empty capability set represents installed equipment whose admission requirements are unmet. Physical execution requires durable transfer evidence. */
class ProductionMachine @JvmOverloads constructor(val id: UUID, val type: String, capabilities: Set<ProductionCapabilityId>, val parallelism: Int = 1,
                                                val execution: ProductionExecution = ProductionExecution.CONTROLLED) {
    val capabilities: Set<ProductionCapabilityId> = Collections.unmodifiableSet(capabilities.toSet())
    init { require(type.isNotBlank() && parallelism in 1..64) }
}
Source

src/main/kotlin/dev/nebulastorage/api/production/ProductionModels.kt; editorial/fr.json

43b4d12d69e6033c24fa8c39dac383ef2429b446