Skip to content

sonolus.script.archetype

ArchetypeLife

Bases: Record

How an entity contributes to life.

good_increment: int instance-attribute

Life increment for a good judgment.

great_increment: int instance-attribute

Life increment for a great judgment.

miss_increment: int instance-attribute

Life increment for a miss judgment.

perfect_increment: int instance-attribute

Life increment for a perfect judgment.

__pos__() -> Self

Return a copy of the record.

type_var_value(var: TypeVar) -> Any classmethod

Return the value of a type variable.

Parameters:

Name Type Description Default
var TypeVar

The type variable to get the value of.

required

Returns:

Type Description
Any

The value of the type variable.

update(perfect_increment: int | None = None, great_increment: int | None = None, good_increment: int | None = None, miss_increment: int | None = None)

Update the life increments.

EntityRef

Bases: Record

Reference to another entity.

May be used with typing.Any to reference an unknown archetype.

Usage
class MyArchetype(PlayArchetype):
    ref_1: EntityRef[OtherArchetype] = imported()
    ref_2: EntityRef[Any] = imported()

__pos__() -> Self

Return a copy of the record.

archetype() -> type[A] classmethod

Get the archetype type.

archetype_matches() -> bool

Check if entity at the index is precisely of the archetype.

get() -> A

Get the entity.

get_as(archetype: type[_BaseArchetype]) -> _BaseArchetype

Get the entity as the given archetype type.

type_var_value(var: TypeVar) -> Any classmethod

Return the value of a type variable.

Parameters:

Name Type Description Default
var TypeVar

The type variable to get the value of.

required

Returns:

Type Description
Any

The value of the type variable.

with_archetype(archetype: type[T]) -> EntityRef[T]

Return a new reference with the given archetype type.

PlayArchetype

Bases: _BaseArchetype

Base class for play mode archetypes.

Usage
class MyArchetype(PlayArchetype):
    # Set to True if the entity is a note and contributes to combo and score
    # Default is False
    is_scored: bool = True

    imported_field: int = imported()
    exported_field: int = exported()
    entity_memory_field: int = entity_memory()
    shared_memory_field: int = shared_memory()

    @callback(order=1)
    def preprocess(self):
        ...

despawn property writable

Whether the entity should be despawned after this frame.

Setting this to True will despawn the entity.

id: int = 0 class-attribute instance-attribute

The id of the archetype or entity.

If accessed on an entity, always returns the runtime archetype id of the entity, even if it doesn't match the type that was used to access it.

E.g. if an entity of archetype A is accessed via EntityRef[B], the id will still be the id of A.

index: int property

The index of this entity.

is_active: bool property

Whether this entity is active.

is_despawned: bool property

Whether this entity is despawned.

is_scored: bool = False class-attribute

Whether entities of this archetype contribute to combo and score.

is_waiting: bool property

Whether this entity is waiting to be spawned.

key: int | float = -1 class-attribute instance-attribute

An optional key for the archetype.

May be useful to identify an archetype in an inheritance hierarchy without needing to check id.

If accessed on an entity, always returns the runtime key of the entity, even if it doesn't match the type that was used to access it.

E.g. if an entity of archetype A is accessed via EntityRef[B], the key will still be the key of A.

life: ArchetypeLife class-attribute

How this entities of this archetype contribute to life depending on judgment.

name: str | None = None class-attribute

The name of the archetype.

If not set, the name will be the class name.

The name is used in level data.

result: PlayEntityInput property

The result of this entity.

Only meaningful for scored entities.

derive(name: str, is_scored: bool, key: int | float | None = None) -> type[T] classmethod

Derive a new archetype class from this archetype.

Roughly equivalent to returning:

class Derived(cls):
    name = <name>
    is_scored = <is_scored>
    key = <key>  # Only set if key is not None

This is used to create a new archetype with the same fields and callbacks, but with a different name and whether it is scored. Compared to manually subclassing, this method also enables faster compilation when the same base archetype has multiple derived archetypes by compiling callbacks only once for the base archetype.

Parameters:

Name Type Description Default
name str

The name of the new archetype.

required
is_scored bool

Whether the new archetype is scored.

required
key int | float | None

A key that can be accessed via the key property of the new archetype.

None

Returns:

Type Description
type[T]

A new archetype class with the same fields and callbacks as this archetype, but with the given name and

type[T]

whether it is scored.

initialize()

Initialize this entity.

Runs when this entity is spawned.

preprocess()

Perform upfront processing.

Runs first when the level is loaded.

ref() -> EntityRef[Self]

Get a reference to this entity.

Valid both in level data and in callbacks.

should_spawn() -> bool

Return whether the entity should be spawned.

Runs each frame while the entity is the first entity in the spawn queue.

spawn(**kwargs: Any) -> None classmethod

Spawn an entity of this archetype, injecting the given values into entity memory.

Usage
class MyArchetype(PlayArchetype):
    field: int = entity_memory()

def f():
    MyArchetype.spawn(field=123)

Parameters:

Name Type Description Default
**kwargs Any

Entity memory values to inject by field name as defined in the Archetype.

{}

spawn_order() -> float

Return the spawn order of the entity.

Runs when the level is loaded after preprocess.

terminate()

Finalize before despawning.

Runs when the entity is despawned.

touch()

Handle user input.

Runs after update_sequential each frame.

update_parallel()

Perform parallel actions for this frame.

Runs after touch each frame.

This is where most gameplay logic should be placed.

update_sequential()

Perform non-parallel actions for this frame.

Runs first each frame.

This is where logic affecting shared memory should be placed. Other logic should typically be placed in update_parallel for better performance.

PreviewArchetype

Bases: _BaseArchetype

Base class for preview mode archetypes.

Usage
class MyArchetype(PreviewArchetype):
    imported_field: int = imported()
    entity_memory_field: int = entity_memory()
    shared_memory_field: int = shared_memory()

    @callback(order=1)
    def preprocess(self):
        ...

id: int = 0 class-attribute instance-attribute

The id of the archetype or entity.

If accessed on an entity, always returns the runtime archetype id of the entity, even if it doesn't match the type that was used to access it.

E.g. if an entity of archetype A is accessed via EntityRef[B], the id will still be the id of A.

index: int property

The index of this entity.

key: int | float = -1 class-attribute instance-attribute

An optional key for the archetype.

May be useful to identify an archetype in an inheritance hierarchy without needing to check id.

If accessed on an entity, always returns the runtime key of the entity, even if it doesn't match the type that was used to access it.

E.g. if an entity of archetype A is accessed via EntityRef[B], the key will still be the key of A.

name: str | None = None class-attribute

The name of the archetype.

If not set, the name will be the class name.

The name is used in level data.

derive(name: str, is_scored: bool, key: int | float | None = None) -> type[T] classmethod

Derive a new archetype class from this archetype.

Roughly equivalent to returning:

class Derived(cls):
    name = <name>
    is_scored = <is_scored>
    key = <key>  # Only set if key is not None

This is used to create a new archetype with the same fields and callbacks, but with a different name and whether it is scored. Compared to manually subclassing, this method also enables faster compilation when the same base archetype has multiple derived archetypes by compiling callbacks only once for the base archetype.

Parameters:

Name Type Description Default
name str

The name of the new archetype.

required
is_scored bool

Whether the new archetype is scored.

required
key int | float | None

A key that can be accessed via the key property of the new archetype.

None

Returns:

Type Description
type[T]

A new archetype class with the same fields and callbacks as this archetype, but with the given name and

type[T]

whether it is scored.

preprocess()

Perform upfront processing.

Runs first when the level is loaded.

ref() -> EntityRef[Self]

Get a reference to this entity.

Valid both in level data and in callbacks.

render()

Render the entity.

Runs after preprocess.

spawn(**kwargs: Any) -> None classmethod

Spawn an entity of this archetype, injecting the given values into entity memory.

Usage
class MyArchetype(PlayArchetype):
    field: int = entity_memory()

def f():
    MyArchetype.spawn(field=123)

Parameters:

Name Type Description Default
**kwargs Any

Entity memory values to inject by field name as defined in the Archetype.

{}

StandardArchetypeName

Bases: StrEnum

Standard archetype names.

BPM_CHANGE = '#BPM_CHANGE' class-attribute instance-attribute

Bpm change marker

TIMESCALE_CHANGE = '#TIMESCALE_CHANGE' class-attribute instance-attribute

Timescale change marker

StandardImport

Standard import annotations for Archetype fields.

Usage
class MyArchetype(WatchArchetype):
    judgment: StandardImport.JUDGMENT

ACCURACY = Annotated[float, imported(name='#ACCURACY')] class-attribute instance-attribute

The accuracy of the entity.

Automatically supported in watch mode for archetypes with a corresponding scored play mode archetype.

BEAT = Annotated[float, imported(name='#BEAT')] class-attribute instance-attribute

The beat of the entity.

BPM = Annotated[float, imported(name='#BPM')] class-attribute instance-attribute

The bpm, for bpm change markers.

JUDGMENT = Annotated[int, imported(name='#JUDGMENT')] class-attribute instance-attribute

The judgment of the entity.

Automatically supported in watch mode for archetypes with a corresponding scored play mode archetype.

TIMESCALE = Annotated[float, imported(name='#TIMESCALE')] class-attribute instance-attribute

The timescale, for timescale change markers.

WatchArchetype

Bases: _BaseArchetype

Base class for watch mode archetypes.

Usage
class MyArchetype(WatchArchetype):
    imported_field: int = imported()
    entity_memory_field: int = entity_memory()
    shared_memory_field: int = shared_memory()

    @callback(order=1)
    def update_sequential(self):
        ...

id: int = 0 class-attribute instance-attribute

The id of the archetype or entity.

If accessed on an entity, always returns the runtime archetype id of the entity, even if it doesn't match the type that was used to access it.

E.g. if an entity of archetype A is accessed via EntityRef[B], the id will still be the id of A.

index: int property

The index of this entity.

is_active: bool property

Whether this entity is active.

is_scored: bool = False class-attribute

Whether entities of this archetype contribute to combo and score.

key: int | float = -1 class-attribute instance-attribute

An optional key for the archetype.

May be useful to identify an archetype in an inheritance hierarchy without needing to check id.

If accessed on an entity, always returns the runtime key of the entity, even if it doesn't match the type that was used to access it.

E.g. if an entity of archetype A is accessed via EntityRef[B], the key will still be the key of A.

life: ArchetypeLife class-attribute

How this entities of this archetype contribute to life depending on judgment.

name: str | None = None class-attribute

The name of the archetype.

If not set, the name will be the class name.

The name is used in level data.

result: WatchEntityInput property

The result of this entity.

Only meaningful for scored entities.

derive(name: str, is_scored: bool, key: int | float | None = None) -> type[T] classmethod

Derive a new archetype class from this archetype.

Roughly equivalent to returning:

class Derived(cls):
    name = <name>
    is_scored = <is_scored>
    key = <key>  # Only set if key is not None

This is used to create a new archetype with the same fields and callbacks, but with a different name and whether it is scored. Compared to manually subclassing, this method also enables faster compilation when the same base archetype has multiple derived archetypes by compiling callbacks only once for the base archetype.

Parameters:

Name Type Description Default
name str

The name of the new archetype.

required
is_scored bool

Whether the new archetype is scored.

required
key int | float | None

A key that can be accessed via the key property of the new archetype.

None

Returns:

Type Description
type[T]

A new archetype class with the same fields and callbacks as this archetype, but with the given name and

type[T]

whether it is scored.

despawn_time() -> float

Return the despawn time of the entity.

initialize()

Initialize this entity.

Runs when this entity is spawned.

preprocess()

Perform upfront processing.

Runs first when the level is loaded.

ref() -> EntityRef[Self]

Get a reference to this entity.

Valid both in level data and in callbacks.

spawn(**kwargs: Any) -> None classmethod

Spawn an entity of this archetype, injecting the given values into entity memory.

Usage
class MyArchetype(PlayArchetype):
    field: int = entity_memory()

def f():
    MyArchetype.spawn(field=123)

Parameters:

Name Type Description Default
**kwargs Any

Entity memory values to inject by field name as defined in the Archetype.

{}

spawn_time() -> float

Return the spawn time of the entity.

terminate()

Finalize before despawning.

Runs when the entity is despawned.

update_parallel()

Parallel update callback.

Runs after touch each frame.

This is where most gameplay logic should be placed.

update_sequential()

Perform non-parallel actions for this frame.

Runs first each frame.

This is where logic affecting shared memory should be placed. Other logic should typically be placed in update_parallel for better performance.

callback(*, order: int = 0) -> Callable[[T], T]

Annotate a callback with its order.

Callbacks are executed from lowest to highest order. By default, callbacks have an order of 0.

Usage
class MyArchetype(PlayArchetype):
    @callback(order=1)
    def update_sequential(self):
        pass

Parameters:

Name Type Description Default
order int

The order of the callback. Lower values are executed first.

0

entity_data() -> Any

Declare a field as entity data.

Entity data is accessible from other entities, but may only be updated in the preprocess callback and is read-only in other callbacks.

It functions like imported and shares the same underlying storage, except that it is not loaded from a level.

Usage
class MyArchetype(PlayArchetype):
    field: int = entity_data()

entity_info_at(index: int) -> PlayEntityInfo | WatchEntityInfo | PreviewEntityInfo

Retrieve entity info of the entity at the given index.

Available in play, watch, and preview mode.

entity_memory() -> Any

Declare a field as entity memory.

Entity memory is private to the entity and is not accessible from other entities. It may be read or updated in any callback associated with the entity.

Entity memory fields may also be set when an entity is spawned using the spawn() method.

Usage
class MyArchetype(PlayArchetype):
    field: int = entity_memory()

exported(*, name: str | None = None) -> Any

Declare a field as exported.

This is only usable in play mode to export data to be loaded in watch mode.

Exported fields are write-only.

Usage
class MyArchetype(PlayArchetype):
    field: int = exported()
    field_with_explicit_name: int = exported(name="#FIELD")

get_archetype_by_name(name: str) -> AnyArchetype

Return the archetype with the given name in the current mode.

imported(*, name: str | None = None) -> Any

Declare a field as imported.

Imported fields may be loaded from the level.

In watch mode, data may also be loaded from a corresponding exported field in play mode.

Imported fields may only be updated in the preprocess callback, and are read-only in other callbacks.

Usage
class MyArchetype(PlayArchetype):
    field: int = imported()
    field_with_explicit_name: int = imported(name="field_name")

shared_memory() -> Any

Declare a field as shared memory.

Shared memory is accessible from other entities.

Shared memory may be read in any callback, but may only be updated by sequential callbacks (preprocess, update_sequential, and touch).

Usage
class MyArchetype(PlayArchetype):
    field: int = shared_memory()