diff --git a/src/main/java/io/github/legendaryforge/legendary/core/api/activation/ActivationInput.java b/src/main/java/io/github/legendaryforge/legendary/core/api/activation/ActivationInput.java
new file mode 100644
index 0000000..e266d8b
--- /dev/null
+++ b/src/main/java/io/github/legendaryforge/legendary/core/api/activation/ActivationInput.java
@@ -0,0 +1,47 @@
+package io.github.legendaryforge.legendary.core.api.activation;
+
+import io.github.legendaryforge.legendary.core.api.id.ResourceId;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.UUID;
+
+/**
+ * Server-side resolved activation input passed to gates.
+ *
+ *
Attributes must be derived from authoritative state (content/mod owned), not client claims.
+ */
+public final class ActivationInput {
+
+ private final UUID activatorPlayerId;
+ private final ResourceId activationGateKey;
+ private final Map attributes;
+ private final Optional targetRef;
+
+ public ActivationInput(
+ UUID activatorPlayerId,
+ ResourceId activationGateKey,
+ Map attributes,
+ Optional targetRef) {
+ this.activatorPlayerId = Objects.requireNonNull(activatorPlayerId, "activatorPlayerId");
+ this.activationGateKey = Objects.requireNonNull(activationGateKey, "activationGateKey");
+ this.attributes = Map.copyOf(Objects.requireNonNull(attributes, "attributes"));
+ this.targetRef = Objects.requireNonNull(targetRef, "targetRef");
+ }
+
+ public UUID activatorPlayerId() {
+ return activatorPlayerId;
+ }
+
+ public ResourceId activationGateKey() {
+ return activationGateKey;
+ }
+
+ public Map attributes() {
+ return attributes;
+ }
+
+ public Optional targetRef() {
+ return targetRef;
+ }
+}
diff --git a/src/main/java/io/github/legendaryforge/legendary/core/api/activation/ActivationInputResolver.java b/src/main/java/io/github/legendaryforge/legendary/core/api/activation/ActivationInputResolver.java
new file mode 100644
index 0000000..9284d94
--- /dev/null
+++ b/src/main/java/io/github/legendaryforge/legendary/core/api/activation/ActivationInputResolver.java
@@ -0,0 +1,10 @@
+package io.github.legendaryforge.legendary.core.api.activation;
+
+/**
+ * Resolves authoritative activation inputs (attributes/target context) server-side.
+ */
+@FunctionalInterface
+public interface ActivationInputResolver {
+
+ ActivationInput resolve(ActivationService.ActivationAttemptRequest request);
+}
diff --git a/src/main/java/io/github/legendaryforge/legendary/core/api/activation/ActivationService.java b/src/main/java/io/github/legendaryforge/legendary/core/api/activation/ActivationService.java
index 115f0ac..eeedfc1 100644
--- a/src/main/java/io/github/legendaryforge/legendary/core/api/activation/ActivationService.java
+++ b/src/main/java/io/github/legendaryforge/legendary/core/api/activation/ActivationService.java
@@ -15,5 +15,6 @@ record ActivationAttemptRequest(
EncounterDefinition definition,
EncounterContext context,
Optional activationGateKey,
- Optional authorityOverride) {}
+ Optional authorityOverride,
+ Optional targetRef) {}
}
diff --git a/src/main/java/io/github/legendaryforge/legendary/core/internal/activation/DefaultActivationService.java b/src/main/java/io/github/legendaryforge/legendary/core/internal/activation/DefaultActivationService.java
index 77be004..a9146b9 100644
--- a/src/main/java/io/github/legendaryforge/legendary/core/internal/activation/DefaultActivationService.java
+++ b/src/main/java/io/github/legendaryforge/legendary/core/internal/activation/DefaultActivationService.java
@@ -3,6 +3,8 @@
import io.github.legendaryforge.legendary.core.api.activation.ActivationAttemptResult;
import io.github.legendaryforge.legendary.core.api.activation.ActivationAttemptStatus;
import io.github.legendaryforge.legendary.core.api.activation.ActivationDecision;
+import io.github.legendaryforge.legendary.core.api.activation.ActivationInput;
+import io.github.legendaryforge.legendary.core.api.activation.ActivationInputResolver;
import io.github.legendaryforge.legendary.core.api.activation.ActivationService;
import io.github.legendaryforge.legendary.core.api.activation.session.ActivationSessionBeginResult;
import io.github.legendaryforge.legendary.core.api.activation.session.ActivationSessionBeginStatus;
@@ -11,7 +13,6 @@
import io.github.legendaryforge.legendary.core.api.gate.ConditionGate;
import io.github.legendaryforge.legendary.core.api.gate.GateDecision;
import io.github.legendaryforge.legendary.core.api.gate.GateService;
-import io.github.legendaryforge.legendary.core.api.id.ResourceId;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
@@ -20,10 +21,17 @@ public final class DefaultActivationService implements ActivationService {
private final GateService gates;
private final ActivationSessionService sessions;
+ private final ActivationInputResolver inputResolver;
public DefaultActivationService(GateService gates, ActivationSessionService sessions) {
+ this(gates, sessions, DefaultActivationService::defaultResolve);
+ }
+
+ public DefaultActivationService(
+ GateService gates, ActivationSessionService sessions, ActivationInputResolver inputResolver) {
this.gates = Objects.requireNonNull(gates, "gates");
this.sessions = Objects.requireNonNull(sessions, "sessions");
+ this.inputResolver = Objects.requireNonNull(inputResolver, "inputResolver");
}
@Override
@@ -34,59 +42,52 @@ public ActivationAttemptResult attemptActivation(ActivationAttemptRequest reques
// Phase 2: evaluate activation gate (if present)
ActivationDecision decision = request.activationGateKey()
- .map(gateKey -> evaluateGate(gateKey, request, encounterKey))
+ .map(ignored -> evaluateGate(inputResolver.resolve(request), encounterKey))
.orElseGet(ActivationDecision::allow);
if (!decision.allowed()) {
return new ActivationAttemptResult(
- ActivationAttemptStatus.FAILED,
- decision,
- Optional.empty(),
- Optional.empty());
+ ActivationAttemptStatus.FAILED, decision, Optional.empty(), Optional.empty());
}
// Phase 2: begin activation session
- ActivationSessionBeginResult begin = sessions.begin(
- new ActivationSessionService.ActivationSessionBeginRequest(
- request.activatorId(),
- encounterKey,
- request.definition(),
- request.context(),
- request.activationGateKey(),
- decision.attributes()
- )
- );
+ ActivationSessionBeginResult begin = sessions.begin(new ActivationSessionService.ActivationSessionBeginRequest(
+ request.activatorId(),
+ encounterKey,
+ request.definition(),
+ request.context(),
+ request.activationGateKey(),
+ decision.attributes()));
- if (begin.status() == ActivationSessionBeginStatus.CREATED || begin.status() == ActivationSessionBeginStatus.EXISTING) {
+ if (begin.status() == ActivationSessionBeginStatus.CREATED
+ || begin.status() == ActivationSessionBeginStatus.EXISTING) {
return new ActivationAttemptResult(
- ActivationAttemptStatus.SUCCESS,
- decision,
- Optional.of(begin.sessionId()),
- Optional.empty());
+ ActivationAttemptStatus.SUCCESS, decision, Optional.of(begin.sessionId()), Optional.empty());
}
// DENIED => FAILED
ActivationDecision denied = ActivationDecision.deny(begin.reasonCode(), decision.attributes());
- return new ActivationAttemptResult(
- ActivationAttemptStatus.FAILED,
- denied,
- Optional.empty(),
- Optional.empty());
+ return new ActivationAttemptResult(ActivationAttemptStatus.FAILED, denied, Optional.empty(), Optional.empty());
}
- private ActivationDecision evaluateGate(ResourceId gateKey, ActivationAttemptRequest request, EncounterKey encounterKey) {
+ private ActivationDecision evaluateGate(ActivationInput input, EncounterKey encounterKey) {
ConditionGate.GateRequest gateRequest = new ConditionGate.GateRequest(
- gateKey,
- request.activatorId(),
+ input.activationGateKey(),
+ input.activatorPlayerId(),
Optional.of(encounterKey),
Optional.empty(),
Optional.empty(),
- Map.of()
- );
+ input.attributes());
GateDecision gateDecision = gates.evaluate(gateRequest);
return gateDecision.allowed()
? ActivationDecision.allow()
: ActivationDecision.deny(gateDecision.reasonCode(), gateDecision.attributes());
}
+
+ private static ActivationInput defaultResolve(ActivationAttemptRequest request) {
+ // Only invoked when request.activationGateKey() is present (see attemptActivation).
+ return new ActivationInput(
+ request.activatorId(), request.activationGateKey().orElseThrow(), Map.of(), request.targetRef());
+ }
}
diff --git a/src/main/java/io/github/legendaryforge/legendary/core/internal/runtime/DefaultCoreRuntime.java b/src/main/java/io/github/legendaryforge/legendary/core/internal/runtime/DefaultCoreRuntime.java
index 023b76b..c0e52ba 100644
--- a/src/main/java/io/github/legendaryforge/legendary/core/internal/runtime/DefaultCoreRuntime.java
+++ b/src/main/java/io/github/legendaryforge/legendary/core/internal/runtime/DefaultCoreRuntime.java
@@ -1,5 +1,7 @@
package io.github.legendaryforge.legendary.core.internal.runtime;
+import io.github.legendaryforge.legendary.core.api.activation.ActivationInput;
+import io.github.legendaryforge.legendary.core.api.activation.ActivationInputResolver;
import io.github.legendaryforge.legendary.core.api.activation.ActivationService;
import io.github.legendaryforge.legendary.core.api.activation.session.ActivationSessionService;
import io.github.legendaryforge.legendary.core.api.encounter.EncounterManager;
@@ -35,17 +37,12 @@
import io.github.legendaryforge.legendary.core.internal.lifecycle.DefaultServiceRegistry;
import io.github.legendaryforge.legendary.core.internal.registry.DefaultRegistryAccess;
import java.time.Clock;
+import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
-/**
- * Default internal wiring of LegendaryCore runtime components.
- *
- * This provides a platform-agnostic reference implementation that platform adapters may
- * construct and delegate to.
- */
public final class DefaultCoreRuntime implements CoreRuntime {
private final RegistryAccess registries;
@@ -59,9 +56,6 @@ public final class DefaultCoreRuntime implements CoreRuntime {
private final Optional players;
private final Optional parties;
- /**
- * Platform-agnostic default constructor using the internal reference EncounterManager.
- */
public DefaultCoreRuntime() {
this(Optional.empty(), Optional.empty(), Clock.systemUTC());
}
@@ -121,7 +115,10 @@ public DefaultCoreRuntime(Optional players, Optional new ActivationInput(
+ request.activatorId(), request.activationGateKey().orElseThrow(), Map.of(), request.targetRef());
+
+ ActivationService activations = new DefaultActivationService(gates, sessions, activationInputs);
this.services.register(ActivationService.class, activations);
bus.subscribe(
@@ -141,33 +138,6 @@ public DefaultCoreRuntime(Optional players, Optional players,
- Optional parties) {
- this.registries = new DefaultRegistryAccess();
-
- DefaultLifecycle lifecycle = new DefaultLifecycle();
- this.lifecycle = lifecycle;
-
- this.services = new DefaultServiceRegistry(lifecycle);
- this.events = java.util.Objects.requireNonNull(events, "events");
- this.clock = Clock.systemUTC();
- this.encounters = java.util.Objects.requireNonNull(encounters, "encounters");
- this.players = java.util.Objects.requireNonNull(players, "players");
- this.parties = java.util.Objects.requireNonNull(parties, "parties");
- }
-
- private DefaultCoreRuntime(
- EncounterManager encounters, Optional players, Optional parties) {
- this(encounters, new SimpleEventBus(), players, parties);
- }
-
@Override
public RegistryAccess registries() {
return registries;