Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions CONTEXT.md
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,31 @@ the correct authority. Reaped on the shared event TTL, so it is a request, never
`game_bot_invite_intent`, which kept its name through the change that gave it a second operation.
_Avoid_: invite intent, group request, party order

**Bot Controller**:
The durable selector for Legacy, RecordOnly, Cohort, or Frozen behavior. RecordOnly records decisions
and authorizes no bot gameplay. Frozen cancels Foreground Actions and authorizes no new bot gameplay.

**Bot Objective**:
A retained purpose with its destination, stage, deadline, catalog revision, and verified progress.
Tactical interruption does not replace the objective.

**Foreground Action**:
The single movement or cast retained by one Bot Controller generation.

**Candidate**:
A proposed action identified by its action, target, spell, reason, and Bot Objective identity.

**Strategy**:
A composition of typed triggers, defaults, and integer priorities used to select Candidates.

**Sessionless Action Consent**:
A Character-owned core projection of a Package's controller selection. An absent row permits legacy
behavior. Each selection updates consent and clears unclaimed Group Intents in one transaction,
including a same-value selection. Automatic group answers and Group Intent claims require admission
on the owning World Shard. Suppression leaves a pending invitation unanswered. An action admitted
before a later selection may still finish on Realm-core. Admission and membership are separate
transactions across Shards. Consent travels with the Character on Transfer and is deleted with it.

**Home Shard**:
The shard that currently holds a Character's row.

Expand Down
6 changes: 5 additions & 1 deletion crates/lyracore-shared/src/group.rs
Original file line number Diff line number Diff line change
Expand Up @@ -138,10 +138,12 @@ pub enum GroupRefusal {
InvalidLootRules,
/// Another Gateway already claimed this bot invite intent.
IntentAlreadyClaimed,
/// The Package has suppressed session-less actions for this Character.
ActionSuppressed,
}

impl GroupRefusal {
pub const ALL: [Self; 14] = [
pub const ALL: [Self; 15] = [
Self::ActorUnavailable,
Self::InviteSelf,
Self::NoSuchPlayer,
Expand All @@ -156,6 +158,7 @@ impl GroupRefusal {
Self::KickSelf,
Self::InvalidLootRules,
Self::IntentAlreadyClaimed,
Self::ActionSuppressed,
];

pub fn as_tag(self) -> &'static str {
Expand All @@ -174,6 +177,7 @@ impl GroupRefusal {
Self::KickSelf => "group:kick_self",
Self::InvalidLootRules => "group:invalid_loot_rules",
Self::IntentAlreadyClaimed => "group:intent_already_claimed",
Self::ActionSuppressed => "group:action_suppressed",
}
}

Expand Down
19 changes: 19 additions & 0 deletions docs/package-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,17 @@ authoritative; this list is the set of event names and their payload types.
| `on_hp_threshold` | `crate::hooks::HpThresholdPayload` |
| `on_go_used` | `crate::hooks::GoUsedPayload` |

### Session-less action consent

`actor::set_sessionless_action_consent(ctx, character_guid, allowed)` records a core-owned consent
projection during bot setup or controller selection. Call it for repeated selection too: every call
removes that Character's unclaimed Group Intents. Do not call it on decision ticks. Absent consent
preserves Legacy behavior. Core Gates check consent when accepting an invite or claiming an Intent.

The Gateway obtains acknowledged admission from the owning World Shard before it accepts at
Realm-core. Admission committed before a concurrent controller change can finish afterwards; this
ordering does not promise an atomic operation across Shards.

### Encounter kernel

`crate::encounter` holds the encounter state machine and the choreography verbs a Package drives it
Expand Down Expand Up @@ -221,8 +232,16 @@ Gate evidence. `endpoint == from` means no movement was approved, even if a comp
planned. A blocked search holds position. Creature callers retain `nav_step` and its existing
collision-gated fallback.

`nav::coverage_generation(ctx, map_id)` returns the active complete coverage generation when its
Gate is enabled, or `None`. Include it with map and instance in deferred destination keys. A changed
generation invalidates a retained route decision; `None` preserves unknown coverage.

`coverage` is `Unknown` unless every consulted navigation cell matches the active complete
manifest. `VerifiedCells` names that generation and the number of checked cells. It proves only
those cells' derivation, not world coverage or the presence of imported terrain and client
geometry. A Package must measure actual position on later observations to establish advancement
or arrival; the proposed endpoint cannot establish either.

`actor::sessionless_action_gate(ctx, character_guid)` checks current Account Claim and Fence ownership, Character availability, and World Session status before Package gameplay. It permits a missing live entity so Legacy can restore a body. Group admission also requires a live entity and current controller consent.

`nav::LEG_MAX_EXPANSIONS` is the expansion cap used by `nav::route_step`. A Package can reserve that work before selecting movement.
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.

#![allow(unused, clippy::all)]
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};

#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
#[sats(crate = __lib)]
pub(super) struct AdmitSessionlessGroupActionArgs {
pub character_guid: u64,
}

impl From<AdmitSessionlessGroupActionArgs> for super::Reducer {
fn from(args: AdmitSessionlessGroupActionArgs) -> Self {
Self::AdmitSessionlessGroupAction {
character_guid: args.character_guid,
}
}
}

impl __sdk::InModule for AdmitSessionlessGroupActionArgs {
type Module = super::RemoteModule;
}

#[allow(non_camel_case_types)]
/// Extension trait for access to the reducer `admit_sessionless_group_action`.
///
/// Implemented for [`super::RemoteReducers`].
pub trait admit_sessionless_group_action {
/// Request that the remote module invoke the reducer `admit_sessionless_group_action` to run as soon as possible.
///
/// This method returns immediately, and errors only if we are unable to send the request.
/// The reducer will run asynchronously in the future,
/// and this method provides no way to listen for its completion status.
/// /// Use [`admit_sessionless_group_action:admit_sessionless_group_action_then`] to run a callback after the reducer completes.
fn admit_sessionless_group_action(&self, character_guid: u64) -> __sdk::Result<()> {
self.admit_sessionless_group_action_then(character_guid, |_, _| {})
}

/// Request that the remote module invoke the reducer `admit_sessionless_group_action` to run as soon as possible,
/// registering `callback` to run when we are notified that the reducer completed.
///
/// This method returns immediately, and errors only if we are unable to send the request.
/// The reducer will run asynchronously in the future,
/// and its status can be observed with the `callback`.
fn admit_sessionless_group_action_then(
&self,
character_guid: u64,

callback: impl FnOnce(&super::ReducerEventContext, Result<Result<(), String>, __sdk::InternalError>)
+ Send
+ 'static,
) -> __sdk::Result<()>;
}

impl admit_sessionless_group_action for super::RemoteReducers {
fn admit_sessionless_group_action_then(
&self,
character_guid: u64,

callback: impl FnOnce(&super::ReducerEventContext, Result<Result<(), String>, __sdk::InternalError>)
+ Send
+ 'static,
) -> __sdk::Result<()> {
self.imp.invoke_reducer_with_callback(
AdmitSessionlessGroupActionArgs { character_guid },
callback,
)
}
}
2 changes: 2 additions & 0 deletions gateway/src/stdb/bindings/bot_invite_intent_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,15 @@ impl __sdk::__query_builder::HasCols for BotInviteIntent {
/// Provides typed access to indexed columns for query building.
pub struct BotInviteIntentIxCols {
pub id: __sdk::__query_builder::IxCol<BotInviteIntent, u64>,
pub inviter_guid: __sdk::__query_builder::IxCol<BotInviteIntent, u64>,
}

impl __sdk::__query_builder::HasIxCols for BotInviteIntent {
type IxCols = BotInviteIntentIxCols;
fn ix_cols(table_name: &'static str) -> Self::IxCols {
BotInviteIntentIxCols {
id: __sdk::__query_builder::IxCol::new(table_name, "id"),
inviter_guid: __sdk::__query_builder::IxCol::new(table_name, "inviter_guid"),
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.

#![allow(unused, clippy::all)]
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};

#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
#[sats(crate = __lib)]
pub(super) struct DebugEmitSessionlessGroupIntentArgs {
pub character_guid: u64,
pub target_guid: u64,
pub leave: bool,
}

impl From<DebugEmitSessionlessGroupIntentArgs> for super::Reducer {
fn from(args: DebugEmitSessionlessGroupIntentArgs) -> Self {
Self::DebugEmitSessionlessGroupIntent {
character_guid: args.character_guid,
target_guid: args.target_guid,
leave: args.leave,
}
}
}

impl __sdk::InModule for DebugEmitSessionlessGroupIntentArgs {
type Module = super::RemoteModule;
}

#[allow(non_camel_case_types)]
/// Extension trait for access to the reducer `debug_emit_sessionless_group_intent`.
///
/// Implemented for [`super::RemoteReducers`].
pub trait debug_emit_sessionless_group_intent {
/// Request that the remote module invoke the reducer `debug_emit_sessionless_group_intent` to run as soon as possible.
///
/// This method returns immediately, and errors only if we are unable to send the request.
/// The reducer will run asynchronously in the future,
/// and this method provides no way to listen for its completion status.
/// /// Use [`debug_emit_sessionless_group_intent:debug_emit_sessionless_group_intent_then`] to run a callback after the reducer completes.
fn debug_emit_sessionless_group_intent(
&self,
character_guid: u64,
target_guid: u64,
leave: bool,
) -> __sdk::Result<()> {
self.debug_emit_sessionless_group_intent_then(character_guid, target_guid, leave, |_, _| {})
}

/// Request that the remote module invoke the reducer `debug_emit_sessionless_group_intent` to run as soon as possible,
/// registering `callback` to run when we are notified that the reducer completed.
///
/// This method returns immediately, and errors only if we are unable to send the request.
/// The reducer will run asynchronously in the future,
/// and its status can be observed with the `callback`.
fn debug_emit_sessionless_group_intent_then(
&self,
character_guid: u64,
target_guid: u64,
leave: bool,

callback: impl FnOnce(&super::ReducerEventContext, Result<Result<(), String>, __sdk::InternalError>)
+ Send
+ 'static,
) -> __sdk::Result<()>;
}

impl debug_emit_sessionless_group_intent for super::RemoteReducers {
fn debug_emit_sessionless_group_intent_then(
&self,
character_guid: u64,
target_guid: u64,
leave: bool,

callback: impl FnOnce(&super::ReducerEventContext, Result<Result<(), String>, __sdk::InternalError>)
+ Send
+ 'static,
) -> __sdk::Result<()> {
self.imp.invoke_reducer_with_callback(
DebugEmitSessionlessGroupIntentArgs {
character_guid,
target_guid,
leave,
},
callback,
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.

#![allow(unused, clippy::all)]
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};

#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
#[sats(crate = __lib)]
pub(super) struct DebugSetSessionlessActionConsentArgs {
pub character_guid: u64,
pub allowed: bool,
}

impl From<DebugSetSessionlessActionConsentArgs> for super::Reducer {
fn from(args: DebugSetSessionlessActionConsentArgs) -> Self {
Self::DebugSetSessionlessActionConsent {
character_guid: args.character_guid,
allowed: args.allowed,
}
}
}

impl __sdk::InModule for DebugSetSessionlessActionConsentArgs {
type Module = super::RemoteModule;
}

#[allow(non_camel_case_types)]
/// Extension trait for access to the reducer `debug_set_sessionless_action_consent`.
///
/// Implemented for [`super::RemoteReducers`].
pub trait debug_set_sessionless_action_consent {
/// Request that the remote module invoke the reducer `debug_set_sessionless_action_consent` to run as soon as possible.
///
/// This method returns immediately, and errors only if we are unable to send the request.
/// The reducer will run asynchronously in the future,
/// and this method provides no way to listen for its completion status.
/// /// Use [`debug_set_sessionless_action_consent:debug_set_sessionless_action_consent_then`] to run a callback after the reducer completes.
fn debug_set_sessionless_action_consent(
&self,
character_guid: u64,
allowed: bool,
) -> __sdk::Result<()> {
self.debug_set_sessionless_action_consent_then(character_guid, allowed, |_, _| {})
}

/// Request that the remote module invoke the reducer `debug_set_sessionless_action_consent` to run as soon as possible,
/// registering `callback` to run when we are notified that the reducer completed.
///
/// This method returns immediately, and errors only if we are unable to send the request.
/// The reducer will run asynchronously in the future,
/// and its status can be observed with the `callback`.
fn debug_set_sessionless_action_consent_then(
&self,
character_guid: u64,
allowed: bool,

callback: impl FnOnce(&super::ReducerEventContext, Result<Result<(), String>, __sdk::InternalError>)
+ Send
+ 'static,
) -> __sdk::Result<()>;
}

impl debug_set_sessionless_action_consent for super::RemoteReducers {
fn debug_set_sessionless_action_consent_then(
&self,
character_guid: u64,
allowed: bool,

callback: impl FnOnce(&super::ReducerEventContext, Result<Result<(), String>, __sdk::InternalError>)
+ Send
+ 'static,
) -> __sdk::Result<()> {
self.imp.invoke_reducer_with_callback(
DebugSetSessionlessActionConsentArgs {
character_guid,
allowed,
},
callback,
)
}
}
Loading