Repository: League-of-Foundry-Developers/foundry-vtt-types
Pull request #3053 (2025-02-01 11:40:24)

Types and tests for `pixi/groups` and `CanvasGroupMixin`
and associated side effects (including changes to previously done mixins as discussed on discord)


Patch:
diff --git a/src/foundry/client-esm/canvas/sources/base-effect-source.d.mts b/src/foundry/client-esm/canvas/sources/base-effect-source.d.mts
index a928cd7b5..999eb7e63 100644
--- a/src/foundry/client-esm/canvas/sources/base-effect-source.d.mts
+++ b/src/foundry/client-esm/canvas/sources/base-effect-source.d.mts
@@ -52,8 +52,9 @@ declare abstract class BaseEffectSource<
 
   /**
    * Some other object which is responsible for this source.
+   * @privateRemarks In Foundry practice this appears to only ever be `null`, `Token.ConfiguredInstance`, or `EffectsCanvasGroup` in v12
    */
-  object: PlaceableObject | null;
+  object: PlaceableObject.Any | CanvasGroupMixin.AnyMixed | null;
 
   /**
    * The source id linked to this effect source.
diff --git a/src/foundry/client/config.d.mts b/src/foundry/client/config.d.mts
index 62d4ffffb..503970aaa 100644
--- a/src/foundry/client/config.d.mts
+++ b/src/foundry/client/config.d.mts
@@ -3161,7 +3161,7 @@ interface CanvasGroupConstructor extends PixiContainerConstructor {
 
   /**
    * The name of this canvas group
-   * @remarks Not used in EffectsCanvasGroup in v11
+   * @remarks Can be undefined in some cases (e.g `EffectsCanvasGroup`) to prevent other groups using it as a parent
    */
-  groupName?: string;
+  groupName?: string | undefined;
 }
diff --git a/src/foundry/client/hooks.d.mts b/src/foundry/client/hooks.d.mts
index ed67c07e0..005dc595a 100644
--- a/src/foundry/client/hooks.d.mts
+++ b/src/foundry/client/hooks.d.mts
@@ -851,14 +851,16 @@ declare global {
      * The dispatched event name replaces "Group" with the named CanvasGroup subclass, i.e. "drawPrimaryCanvasGroup".
      * @param group - The group being drawn
      */
-    type DrawGroup<G extends CanvasGroupMixin.AnyConstructor = CanvasGroupMixin.AnyConstructor> = (group: G) => void;
+    type DrawGroup<G extends CanvasGroupMixin.AnyMixedConstructor = CanvasGroupMixin.AnyMixedConstructor> = (
+      group: G,
+    ) => void;
 
     /**
      * A hook event that fires when a {@link CanvasGroup} is deconstructed.
      * The dispatched event name replaces "Group" with the named CanvasGroup subclass, i.e. "tearDownPrimaryCanvasGroup".
      * @param group - The group being deconstructed
      */
-    type TearDownGroup<G extends CanvasGroupMixin.AnyConstructor = CanvasGroupMixin.AnyConstructor> = (
+    type TearDownGroup<G extends CanvasGroupMixin.AnyMixedConstructor = CanvasGroupMixin.AnyMixedConstructor> = (
       group: G,
     ) => void;
 
diff --git a/src/foundry/client/pixi/core/containers/base-canvas-group.d.mts b/src/foundry/client/pixi/core/containers/base-canvas-group.d.mts
index 96df16f47..f1f524447 100644
--- a/src/foundry/client/pixi/core/containers/base-canvas-group.d.mts
+++ b/src/foundry/client/pixi/core/containers/base-canvas-group.d.mts
@@ -1,36 +1,39 @@
-import type { AnyObject, Mixin, PrettifyType, RemoveIndexSignatures } from "fvtt-types/utils";
+import type { FixedInstanceType, HandleEmptyObject, PrettifyType, RemoveIndexSignatures } from "fvtt-types/utils";
 
 declare const DynamicClass: new <_Computed extends object>(arg0: never, ...args: never[]) => _Computed;
 
 // @ts-expect-error - This is a workaround to allow for dynamic top level properties in a class.
 declare class LayersClass<
   Group extends CanvasGroupMixin.LayerGroup | NoLayerGroup,
-  Instance extends object = RemoveIndexSignatures<GroupFor<Group>>,
+  Instance extends object = RemoveIndexSignatures<CanvasGroupMixin.LayersFor<Group>>,
 > extends DynamicClass<Instance> {}
 
-type GroupFor<Group extends CanvasGroupMixin.LayerGroup | NoLayerGroup> = Group extends CanvasGroupMixin.LayerGroup
-  ? CanvasGroupMixin.LayersFor<Group>
-  : // The empty object is useful to merge nothing in when necessary.
-    // eslint-disable-next-line @typescript-eslint/no-empty-object-type
-    {};
-
-declare class CanvasGroup<Group extends CanvasGroupMixin.LayerGroup | NoLayerGroup> extends LayersClass<Group> {
-  /** @privateRemarks All mixin classses should accept anything for its constructor. */
-  constructor(...args: any[]);
-
-  /** @defaultValue `true` */
-  sortableChildren: boolean;
-
+// Note(LukeAbby): This interface has been separated out to simplify the constructor edge cases.
+// By avoiding having the instance side it makes the class more malleable and allows generics.
+interface CanvasGroupStatic<Group extends CanvasGroupMixin.LayerGroup | NoLayerGroup> {
   /**
    * The name of this canvas group
    * @remarks Foundry marked as abstract
    */
-  static groupName: string;
+  groupName: Group extends NoLayerGroup ? undefined : Group;
 
   /**
    * If this canvas group should teardown non-layers children.
+   * @defaultValue `true`
    */
-  static tearDownChildren: boolean;
+  tearDownChildren: boolean;
+}
+
+declare class CanvasGroup<
+  Group extends CanvasGroupMixin.LayerGroup | NoLayerGroup,
+  DrawOptions extends CanvasGroupMixin.DrawOptions = CanvasGroupMixin.DrawOptions,
+  TearDownOptions extends CanvasGroupMixin.TearDownOptions = CanvasGroupMixin.TearDownOptions,
+> extends LayersClass<Group> {
+  /** @privateRemarks All mixin classses should accept anything for its constructor. */
+  constructor(...args: any[]);
+
+  /** @defaultValue `true` */
+  sortableChildren: boolean;
 
   /**
    * The canonical name of the canvas group is the name of the constructor that is the immediate child of the defined base class.
@@ -45,37 +48,45 @@ declare class CanvasGroup<Group extends CanvasGroupMixin.LayerGroup | NoLayerGro
 
   /**
    * A mapping of CanvasLayer classes which belong to this group.
-   * @remarks Default value defined by this._createLayers, which pulls from CONFIG.Canvas.layers
+   * @remarks Default value defined by this._createLayers, which is called in the constructor, and   pulls from CONFIG.Canvas.layers
    */
-  layers: GroupFor<Group>;
+  layers: CanvasGroupMixin.LayersFor<Group>;
 
   /**
    * Create CanvasLayer instances which belong to the canvas group.
    */
-  _createLayers(): GroupFor<Group>;
+  protected _createLayers(): CanvasGroupMixin.LayersFor<Group>;
 
   /** Draw the canvas group and all its component layers. */
-  draw(options: CanvasGroupMixin.DrawOptions): Promise<void>;
+  draw(options?: HandleEmptyObject<DrawOptions>): Promise<this>;
 
   /**
    * Draw the canvas group and all its component layers.
    */
-  protected _draw(options: CanvasGroupMixin.DrawOptions): Promise<void>;
+  protected _draw(options?: HandleEmptyObject<DrawOptions>): Promise<void>;
 
   /**
    * Remove and destroy all layers from the base canvas.
    */
-  tearDown(options: CanvasGroupMixin.TearDownOptions): Promise<void>;
+  tearDown(options: HandleEmptyObject<TearDownOptions>): Promise<this>;
 
   /**
    * Remove and destroy all layers from the base canvas.
    */
-  protected _tearDown(options: CanvasGroupMixin.TearDownOptions): Promise<void>;
+  protected _tearDown(options: HandleEmptyObject<TearDownOptions>): Promise<void>;
 }
 
 declare const _NoLayerGroup: unique symbol;
 type NoLayerGroup = typeof _NoLayerGroup;
 
+type ApplyGroup<
+  BaseClass extends CanvasGroupMixin.BaseClass,
+  Group extends CanvasGroupMixin.LayerGroup | NoLayerGroup,
+> = CanvasGroupStatic<Group> &
+  (new <DrawOptions extends CanvasGroupMixin.DrawOptions, TearDownOptions extends CanvasGroupMixin.TearDownOptions>(
+    ...args: ConstructorParameters<BaseClass>
+  ) => CanvasGroup<Group, DrawOptions, TearDownOptions> & FixedInstanceType<BaseClass>);
+
 declare global {
   /**
    * A mixin which decorates any container with base canvas common properties.
@@ -84,21 +95,35 @@ declare global {
    */
   function CanvasGroupMixin<
     BaseClass extends CanvasGroupMixin.BaseClass,
-    // In `_createLayers` the co`de assigns top level properties to the class.
+    // In `_createLayers` the code assigns top level properties to the class.
     // This is why Group exists.`
     Group extends CanvasGroupMixin.LayerGroup | NoLayerGroup = NoLayerGroup,
-  >(
-    ContainerClass: BaseClass,
-  ): Mixin<typeof CanvasGroup<Group>, BaseClass> & { groupName: Group extends NoLayerGroup ? string : Group };
+  >(ContainerClass: BaseClass): CanvasGroupMixin.Mixed<BaseClass, Group>;
+
+  /**
+   * @deprecated since v12, until 14
+   * @remarks "BaseCanvasMixin is deprecated in favor of CanvasGroupMixin"
+   */
+  const BaseCanvasMixin: typeof CanvasGroupMixin;
 
   namespace CanvasGroupMixin {
-    type BaseClass = typeof AnyPIXIContainer;
-    type AnyConstructor = typeof AnyCanvasGroup;
-    type Any = AnyCanvasGroup;
+    type AnyMixedConstructor = ReturnType<
+      typeof CanvasGroupMixin<CanvasGroupMixin.BaseClass, CanvasGroupMixin.LayerGroup | NoLayerGroup>
+    >;
+    interface AnyMixed extends AnyMixedConstructor {}
 
-    type DrawOptions = AnyObject;
+    type BaseClass = PIXI.Container.AnyConstructor;
 
-    type TearDownOptions = AnyObject;
+    type Mixed<
+      BaseClass extends CanvasGroupMixin.BaseClass,
+      Group extends CanvasGroupMixin.LayerGroup | NoLayerGroup,
+    > = BaseClass & ApplyGroup<BaseClass, Group>;
+
+    // eslint-disable-next-line @typescript-eslint/no-empty-object-type
+    interface DrawOptions {}
+
+    // eslint-disable-next-line @typescript-eslint/no-empty-object-type
+    interface TearDownOptions {}
 
     interface Layers {
       readonly [key: string]: CanvasLayer;
@@ -106,7 +131,7 @@ declare global {
 
     type LayerGroup = keyof CONFIG["Canvas"]["groups"];
 
-    type LayersFor<T extends LayerGroup> = PrettifyType<
+    type LayersFor<T extends LayerGroup | NoLayerGroup> = PrettifyType<
       _FilterOutNever<{
         readonly [K in keyof typeof Canvas.layers]: (typeof Canvas.layers)[K] extends {
           readonly layerClass?: abstract new (...args: infer _1) => infer LayerInstance extends CanvasLayer;
@@ -127,7 +152,3 @@ declare global {
 declare abstract class AnyCanvasGroup extends CanvasGroup<NoLayerGroup> {
   constructor(arg0: never, ...args: never[]);
 }
-
-declare abstract class AnyPIXIContainer extends PIXI.Container<any> {
-  constructor(arg0: never, ...args: never[]);
-}
diff --git a/src/foundry/client/pixi/core/containers/quadtree.d.mts b/src/foundry/client/pixi/core/containers/quadtree.d.mts
index 935450c10..1a08b75b1 100644
--- a/src/foundry/client/pixi/core/containers/quadtree.d.mts
+++ b/src/foundry/client/pixi/core/containers/quadtree.d.mts
@@ -243,7 +243,7 @@ declare global {
     interface Any extends AnyCanvasQuadtree {}
     type AnyConstructor = typeof AnyCanvasQuadtree;
 
-    type CanvasQuadtreeObject = PrimaryCanvasObjectMixin.Any | PlaceableObject.Any;
+    type CanvasQuadtreeObject = PrimaryCanvasObjectMixin.AnyMixed | PlaceableObject.Any;
   }
 }
 
diff --git a/src/foundry/client/pixi/core/interaction/canvas-animation.d.mts b/src/foundry/client/pixi/core/interaction/canvas-animation.d.mts
index 4ab2de842..13f1f9861 100644
--- a/src/foundry/client/pixi/core/interaction/canvas-animation.d.mts
+++ b/src/foundry/client/pixi/core/interaction/canvas-animation.d.mts
@@ -48,7 +48,10 @@ declare global {
      * CanvasAnimation.animate(attributes, {duration:500});
      * ```
      */
-    static animate(attributes: CanvasAnimation.Attribute[], options?: CanvasAnimationOptions): Promise<boolean | void>;
+    static animate(
+      attributes: CanvasAnimation.Attribute[],
+      options?: CanvasAnimationOptions,
+    ): CanvasAnimation.AnimateReturn;
 
     /**
      * Retrieve an animation currently in progress by its name
@@ -89,6 +92,9 @@ declare global {
     interface Any extends AnyCanvasAnimation {}
     type AnyConstructor = typeof AnyCanvasAnimation;
 
+    /** @remarks Helper type as many things `return CanvasAnimation.animate(...)` */
+    type AnimateReturn = Promise<boolean | void>;
+
     type EasingFunction = CoreEasingFunctions | ((percent: number) => number);
     type CoreEasingFunctions = PropertiesOfType<typeof CanvasAnimation, (percent: number) => number>;
 
diff --git a/src/foundry/client/pixi/groups/effects.d.mts b/src/foundry/client/pixi/groups/effects.d.mts
index ec5e9c5f8..6d590f312 100644
--- a/src/foundry/client/pixi/groups/effects.d.mts
+++ b/src/foundry/client/pixi/groups/effects.d.mts
@@ -1,19 +1,31 @@
-import type { ValueOf } from "fvtt-types/utils";
+import type { HandleEmptyObject, NullishProps } from "fvtt-types/utils";
 
 declare global {
   /**
    * A container group which contains visual effects rendered above the primary group.
+   *
+   * TODO:
+   *  The effects canvas group is now only performing shape initialization, logic that needs to happen at
+   *  the placeable or object level is now their burden.
+   *  - [DONE] Adding or removing a source from the EffectsCanvasGroup collection.
+   *  - [TODO] A change in a darkness source should re-initialize all overlaping light and vision source.
+   *
+   * ### Hook Events
+   * - {@link hookEvents.lightingRefresh}
    */
-  class EffectsCanvasGroup extends CanvasGroupMixin(PIXI.Container) {
-    constructor();
-
+  class EffectsCanvasGroup<
+    DrawOptions extends EffectsCanvasGroup.DrawOptions = EffectsCanvasGroup.DrawOptions,
+    TearDownOptions extends EffectsCanvasGroup.TearDownOptions = EffectsCanvasGroup.TearDownOptions,
+  > extends CanvasGroupMixin(PIXI.Container)<DrawOptions, TearDownOptions> {
     /**
      * Whether to currently animate light sources.
+     * @defaultValue `true`
      */
     animateLightSources: boolean;
 
     /**
      * Whether to currently animate vision sources.
+     * @defaultValue `true`
      */
     animateVisionSources: boolean;
 
@@ -73,7 +85,7 @@ declare global {
      */
     clearEffects(): void;
 
-    protected override _draw(options: CanvasGroupMixin.DrawOptions): Promise<void>;
+    protected override _draw(options: HandleEmptyObject<DrawOptions>): Promise<void>;
 
     /**
      * Initialize positive light sources which exist within the active Scene.
@@ -92,17 +104,17 @@ declare global {
     /**
      * Refresh the state and uniforms of all light sources and darkness sources objects.
      */
-    refreshLightSources(): boolean;
+    refreshLightSources(): void;
 
     /**
      * Refresh the state and uniforms of all VisionSource objects.
      */
-    refreshVisionSources(): boolean;
+    refreshVisionSources(): void;
 
     /**
      * Refresh the active display of lighting.
      */
-    refreshLighting(): boolean;
+    refreshLighting(): void;
 
     /**
      * Test whether the point is inside light.
@@ -117,8 +129,9 @@ declare global {
      * @param point     - The point.
      * @param elevation - The elevation of the point.
      * @returns Is inside darkness?
+     * @remarks Foundry does not use the `elevation` parameter
      */
-    testInsideDarkness(point: Canvas.Point, elevation: number): boolean;
+    testInsideDarkness(point: Canvas.Point, _elevation?: number): boolean;
 
     /**
      * Get the darkness level at the given point.
@@ -128,14 +141,14 @@ declare global {
      */
     getDarknessLevel(point: Canvas.Point, elevation: number): number;
 
-    override _tearDown(options: CanvasGroupMixin.TearDownOptions): Promise<void>;
+    override _tearDown(options: HandleEmptyObject<TearDownOptions>): Promise<void>;
 
     /**
      * Activate vision masking for visual effects
      * @param enabled - Whether to enable or disable vision masking
      *                  (default: `true`)
      */
-    toggleMaskingFilters(enabled: boolean): void;
+    toggleMaskingFilters(enabled?: boolean): void;
 
     /**
      * Activate post-processing effects for a certain effects channel.
@@ -144,9 +157,9 @@ declare global {
      * @param uniforms            - The uniforms to update.
      */
     activatePostProcessingFilters(
-      filterMode: ValueOf<VisualEffectsMaskingFilter.FILTER_MODES>,
-      postProcessingModes: VisualEffectsMaskingFilter.PostProcessModes,
-      uniforms: AbstractBaseShader.Uniforms,
+      filterMode: VisualEffectsMaskingFilter.FILTER_MODES,
+      postProcessingModes?: VisualEffectsMaskingFilter.PostProcessModes,
+      uniforms?: AbstractBaseShader.Uniforms,
     ): void;
 
     /**
@@ -172,24 +185,53 @@ declare global {
      */
     animateDarkness(
       target?: number,
-      {
-        duration,
-      }?: {
-        /**
-         * The desired animation time in milliseconds. Default is 10 seconds
-         * @defaultValue 10000
-         */
-        duration?: number;
-      },
-    ): ReturnType<typeof CanvasAnimation.animate>;
+      options?: EffectsCanvasGroup.AnimateDarknessOptions,
+    ): CanvasAnimation.AnimateReturn;
+
+    /**
+     * @deprecated since v12, until v14
+     * @remarks "EffectsCanvasGroup#visibility has been deprecated and moved to Canvas#visibility."
+     */
+    get visibility(): Canvas["visibility"];
+
+    /**
+     * @deprecated since v12, until v14
+     * @remarks "EffectsCanvasGroup#globalLightSource has been deprecated and moved to EnvironmentCanvasGroup#globalLightSource."
+     */
+    get globalLightSource(): Canvas["environment"]["globalLightSource"];
+
+    /**
+     * @deprecated since v12
+     * @remarks "EffectsCanvasGroup#updateGlobalLightSource has been deprecated and is part of EnvironmentCanvasGroup#initialize workflow."
+     */
+    updateGlobalLightSource(): void;
   }
 
   namespace EffectsCanvasGroup {
-    type Any = AnyEffectsCanvasGroup;
+    interface Any extends AnyEffectsCanvasGroup {}
     type AnyConstructor = typeof AnyEffectsCanvasGroup;
+
+    /** @internal */
+    type _AnimateDarknessOptions = NullishProps<{
+      /**
+       * The desired animation time in milliseconds. Default is 10 seconds
+       * @defaultValue `10000`
+       * @remarks Only has a parameter default; `null` is effectively `0`, resulting in no animation, just instant darkness
+       */
+      duration: number;
+    }>;
+
+    interface AnimateDarknessOptions extends _AnimateDarknessOptions {}
+
+    interface DrawOptions extends CanvasGroupMixin.DrawOptions {}
+
+    interface TearDownOptions extends CanvasGroupMixin.TearDownOptions {}
   }
 }
 
-declare abstract class AnyEffectsCanvasGroup extends EffectsCanvasGroup {
+declare abstract class AnyEffectsCanvasGroup extends EffectsCanvasGroup<
+  EffectsCanvasGroup.DrawOptions,
+  EffectsCanvasGroup.TearDownOptions
+> {
   constructor(arg0: never, ...args: never[]);
 }
diff --git a/src/foundry/client/pixi/groups/environment.d.mts b/src/foundry/client/pixi/groups/environment.d.mts
index cf8fe0288..f2350ccc2 100644
--- a/src/foundry/client/pixi/groups/environment.d.mts
+++ b/src/foundry/client/pixi/groups/environment.d.mts
@@ -1,45 +1,50 @@
-import type { InexactPartial } from "fvtt-types/utils";
-
-// TODO: Remove when this is typed as part of #2674
-type SceneEnvironmentData = unknown;
+import type { FixedInstanceType, HandleEmptyObject, InexactPartial, NullishProps } from "fvtt-types/utils";
+import type { SceneEnvironmentData } from "../../../common/documents/_types.d.mts";
 
 declare global {
   /**
    * A container group which contains the primary canvas group and the effects canvas group.
    */
-  class EnvironmentCanvasGroup extends CanvasGroupMixin<typeof PIXI.Container, "environment">(PIXI.Container) {
+  class EnvironmentCanvasGroup<
+    DrawOptions extends EnvironmentCanvasGroup.DrawOptions = EnvironmentCanvasGroup.DrawOptions,
+    TearDownOptions extends EnvironmentCanvasGroup.TearDownOptions = EnvironmentCanvasGroup.TearDownOptions,
+  > extends CanvasGroupMixin<typeof PIXI.Container, "environment">(PIXI.Container)<DrawOptions, TearDownOptions> {
     /**
      * The global light source attached to the environment
+     * @remarks This is `defineProperty`'d as non-writable and non-configurable at construction
      */
-    globalLightSource(): foundry.canvas.sources.GlobalLightSource;
+    readonly globalLightSource: FixedInstanceType<typeof CONFIG.Canvas.globalLightSourceClass>;
 
     /**
+     * Should this group tear down its non-layer children?
      * @defaultValue `false`
      */
     static override tearDownChildren: boolean;
 
     /**
      * Colors exposed by the manager.
+     * @remarks Properties here are only `undefined` prior to `#initialize`ation
      */
-    colors: Record<string, Color | undefined>;
+    colors: EnvironmentCanvasGroup.Colors;
 
     /**
      * Weights used by the manager to compute colors.
+     * @remarks Properties here are only `undefined` prior to `#initialize`ation
      */
-    weights: Record<string, number | undefined>;
+    weights: EnvironmentCanvasGroup.Weights;
 
     /**
      * Get the darkness level of this scene.
      */
     get darknessLevel(): number;
 
-    protected override _draw(options: CanvasGroupMixin.DrawOptions): Promise<void>;
+    protected override _draw(options: HandleEmptyObject<DrawOptions>): Promise<void>;
 
     /**
      * Initialize the scene environment options.
      * @remarks `@fires PIXI.FederatedEvent type: "darknessChange" - event: {environmentData: {darknessLevel, priorDarknessLevel}}`
      */
-    initialize(config: InexactPartial<EnvironmentCanvasGroup.InitializeConfig>): void;
+    initialize(config?: EnvironmentCanvasGroup.InitializeOptions): void;
 
     /**
      * @deprecated since v12, will be removed in v14
@@ -49,10 +54,34 @@ declare global {
   }
 
   namespace EnvironmentCanvasGroup {
-    type Any = AnyEnvironmentCanvasGroup;
+    interface Any extends AnyEnvironmentCanvasGroup {}
     type AnyConstructor = typeof AnyEnvironmentCanvasGroup;
 
-    interface InitializeConfig {
+    interface DrawOptions extends CanvasGroupMixin.DrawOptions {}
+
+    interface TearDownOptions extends CanvasGroupMixin.TearDownOptions {}
+
+    type ColorKeys =
+      | "darkness"
+      | "halfdark"
+      | "background"
+      | "dim"
+      | "bright"
+      | "ambientBrightest"
+      | "ambientDaylight"
+      | "ambientDarkness"
+      | "sceneBackground"
+      | "fogExplored"
+      | "fogUnexplored";
+
+    interface Colors extends Record<EnvironmentCanvasGroup.ColorKeys, Color | undefined> {}
+
+    type WeightKeys = "dark" | "halfdark" | "dim" | "bright";
+
+    interface Weights extends Record<EnvironmentCanvasGroup.WeightKeys, number | undefined> {}
+
+    /** @internal */
+    type _InitializeOptions = NullishProps<{
       /** The background canvas color */
       backgroundColor: Color.Source;
 
@@ -71,12 +100,30 @@ declare global {
       /** The color applied to unexplored areas */
       fogUnexploredColor: Color.Source;
 
-      /** The scene environment data */
-      environment: SceneEnvironmentData;
-    }
+      /**
+       * @deprecated since v12 until v14
+       * @remarks "config.darknessLevel parameter into EnvironmentCanvasGroup#initialize is deprecated.
+       * You should pass the darkness level into config.environment.darknessLevel"
+       * @privateRemarks [sic]
+       */
+      darknessLevel: number;
+    }> &
+      InexactPartial<{
+        /**
+         * The scene environment data
+         * @defaultValue `{}`
+         * @remarks Can't be `null` as it only has a parameter default and has properties accessed
+         */
+        environment: NullishProps<SceneEnvironmentData>;
+      }>;
+
+    interface InitializeOptions extends _InitializeOptions {}
   }
 }
 
-declare abstract class AnyEnvironmentCanvasGroup extends EnvironmentCanvasGroup {
+declare abstract class AnyEnvironmentCanvasGroup extends EnvironmentCanvasGroup<
+  EnvironmentCanvasGroup.DrawOptions,
+  EnvironmentCanvasGroup.TearDownOptions
+> {
   constructor(arg0: never, ...args: never[]);
 }
diff --git a/src/foundry/client/pixi/groups/hidden.d.mts b/src/foundry/client/pixi/groups/hidden.d.mts
index 2e997c6f4..663d250e8 100644
--- a/src/foundry/client/pixi/groups/hidden.d.mts
+++ b/src/foundry/client/pixi/groups/hidden.d.mts
@@ -1,14 +1,17 @@
-import type { EventMode } from "pixi.js";
+import type { HandleEmptyObject } from "fvtt-types/utils";
 
 declare global {
   /**
    * A specialized canvas group for rendering hidden containers before all others (like masks).
    */
-  class HiddenCanvasGroup extends CanvasGroupMixin<typeof PIXI.Container, "hidden">(PIXI.Container) {
+  class HiddenCanvasGroup<
+    DrawOptions extends HiddenCanvasGroup.DrawOptions = HiddenCanvasGroup.DrawOptions,
+    TearDownOptions extends HiddenCanvasGroup.TearDownOptions = HiddenCanvasGroup.TearDownOptions,
+  > extends CanvasGroupMixin<typeof PIXI.Container, "hidden">(PIXI.Container)<DrawOptions, TearDownOptions> {
     /**
      * @defaultValue `"none"`
      */
-    override eventMode: EventMode;
+    override eventMode: PIXI.EventMode;
 
     /**
      * The container which hold masks.
@@ -20,25 +23,34 @@ declare global {
      * @param name          - Name of the mask.
      * @param displayObject - Display object to add.
      * @param position      - Position of the mask.
+     * @throws If `displayObject` doesn't implement a `clear` method, or if `name` is an empty string
+     * @remarks `position` is not used if fasley
      */
-    addMask(name: string, displayObject: PIXI.DisplayObject, position?: number): void;
+    addMask(name: string, displayObject: PIXI.DisplayObject, position?: number | null): void;
 
     /**
      * Invalidate the masks: flag them for rerendering.
      */
     invalidateMasks(): void;
 
-    protected override _draw(options: CanvasGroupMixin.DrawOptions): Promise<void>;
+    protected override _draw(options: HandleEmptyObject<DrawOptions>): Promise<void>;
 
-    protected override _tearDown(options: CanvasGroupMixin.TearDownOptions): Promise<void>;
+    protected override _tearDown(options: HandleEmptyObject<TearDownOptions>): Promise<void>;
   }
 
   namespace HiddenCanvasGroup {
-    type Any = AnyHiddenCanvasGroup;
+    interface Any extends AnyHiddenCanvasGroup {}
     type AnyConstructor = typeof AnyHiddenCanvasGroup;
+
+    interface DrawOptions extends CanvasGroupMixin.DrawOptions {}
+
+    interface TearDownOptions extends CanvasGroupMixin.TearDownOptions {}
   }
 }
 
-declare abstract class AnyHiddenCanvasGroup extends HiddenCanvasGroup {
+declare abstract class AnyHiddenCanvasGroup extends HiddenCanvasGroup<
+  HiddenCanvasGroup.DrawOptions,
+  HiddenCanvasGroup.TearDownOptions
+> {
   constructor(arg0: never, ...args: never[]);
 }
diff --git a/src/foundry/client/pixi/groups/interface.d.mts b/src/foundry/client/pixi/groups/interface.d.mts
index 141ab5196..85229a764 100644
--- a/src/foundry/client/pixi/groups/interface.d.mts
+++ b/src/foundry/client/pixi/groups/interface.d.mts
@@ -1,24 +1,28 @@
-import type { InexactPartial } from "fvtt-types/utils";
+import type { HandleEmptyObject, InexactPartial, NullishProps } from "fvtt-types/utils";
 
 declare global {
   /**
    * A container group which displays interface elements rendered above other canvas groups.
    */
-  class InterfaceCanvasGroup extends CanvasGroupMixin<typeof PIXI.Container<any>, "interface">(PIXI.Container) {
+  class InterfaceCanvasGroup<
+    DrawOptions extends InterfaceCanvasGroup.DrawOptions = InterfaceCanvasGroup.DrawOptions,
+    TearDownOptions extends InterfaceCanvasGroup.TearDownOptions = InterfaceCanvasGroup.TearDownOptions,
+  > extends CanvasGroupMixin<typeof PIXI.Container, "interface">(PIXI.Container)<DrawOptions, TearDownOptions> {
     /**
      * Add a PrimaryGraphics to the group.
      * @param drawing - The Drawing being added
      * @returns The created Graphics instance
+     * @remarks
      */
-    addDrawing(drawing: Drawing): PIXI.Graphics;
+    addDrawing(drawing: Drawing.ConfiguredInstance): PIXI.Graphics;
 
     /**
      * Remove a PrimaryGraphics from the group.
      * @param drawing - The Drawing being removed
      */
-    removeDrawing(drawing: Drawing): void;
+    removeDrawing(drawing: Drawing.ConfiguredInstance): void;
 
-    protected override _draw(options: CanvasGroupMixin.DrawOptions): Promise<void>;
+    protected override _draw(options: HandleEmptyObject<DrawOptions>): Promise<void>;
 
     /**
      * Display scrolling status text originating from this ObjectHUD container.
@@ -26,50 +30,66 @@ declare global {
      * @param content - The text content to display
      * @param options - Options which customize the text animation
      * @returns - The created PreciseText object which is scrolling
+     * @remarks Only returns `null` if the core `scrollingStatusText` setting is falsey
      */
     createScrollingText(
       origin: Canvas.Point,
       content: string,
-      options?: {
-        /**
-         * The duration of the scrolling effect in milliseconds
-         * @defaultValue `2000`
-         */
-        duration?: number;
+      options?: InterfaceCanvasGroup.CreateScrollingTextOptions,
+    ): Promise<PreciseText | null>;
+  }
 
-        /**
-         * The distance in pixels that the scrolling text should travel
-         * @defaultValue (2 * text.width)
-         */
-        distance?: number;
+  namespace InterfaceCanvasGroup {
+    interface Any extends AnyInterfaceCanvasGroup {}
+    type AnyConstructor = typeof AnyInterfaceCanvasGroup;
 
-        /**
-         * The original anchor point where the text appears
-         */
-        anchor: foundry.CONST.TEXT_ANCHOR_POINTS;
+    /** @internal */
+    type _CreateScrollingTextOptions = NullishProps<{
+      /**
+       * The distance in pixels that the scrolling text should travel
+       * @defaultValue Double the width or height of the text, depending on direction
+       * @remarks Default provided by `??`
+       */
+      distance: number;
 
-        /**
-         * The direction in which the text scrolls
-         */
-        direction: foundry.CONST.TEXT_ANCHOR_POINTS;
+      /**
+       * The original anchor point where the text appears
+       * @defaultValue `CONST.TEXT_ANCHOR_POINTS.CENTER`
+       */
+      anchor: foundry.CONST.TEXT_ANCHOR_POINTS;
 
-        /**
-         * An amount of randomization between [0, 1] applied to the initial position
-         * @defaultValue `0`
-         */
-        jitter: number;
+      /**
+       * The direction in which the text scrolls
+       * @defaultValue `CONST.TEXT_ANCHOR_POINTS.TOP`
+       */
+      direction: foundry.CONST.TEXT_ANCHOR_POINTS;
 
+      /**
+       * An amount of randomization between [0, 1] applied to the initial position
+       * @defaultValue `0`
+       * @remarks Only used if truthy
+       */
+      jitter: number;
+    }> &
+      InexactPartial<{
         /**
-         * Additional parameters of PIXI.TextStyle which are applied to the text
+         * The duration of the scrolling effect in milliseconds
+         * @defaultValue `2000`
+         * @remarks Can't be `null` as it only has a parameter default and a duration of `0` is not appropriate
          */
-        textStyle: InexactPartial<PIXI.ITextStyle>;
-      },
-    ): Promise<PreciseText | null>;
-  }
+        duration: number;
+      }> &
+      /**
+       * Additional parameters of PIXI.TextStyle which are applied to the text
+       * @remarks Excess keys are collected as `{...textStyle}` and passed to {@link PreciseText.getTextStyle} which checks for `!== undefined`, so this can't be NullishProps
+       */
+      InexactPartial<PIXI.ITextStyle>;
 
-  namespace InterfaceCanvasGroup {
-    type Any = AnyInterfaceCanvasGroup;
-    type AnyConstructor = typeof AnyInterfaceCanvasGroup;
+    interface CreateScrollingTextOptions extends _CreateScrollingTextOptions {}
+
+    interface DrawOptions extends CanvasGroupMixin.DrawOptions {}
+
+    interface TearDownOptions extends CanvasGroupMixin.TearDownOptions {}
   }
 }
 
diff --git a/src/foundry/client/pixi/groups/overlay.d.mts b/src/foundry/client/pixi/groups/overlay.d.mts
index d453af38f..27d7ae4c5 100644
--- a/src/foundry/client/pixi/groups/overlay.d.mts
+++ b/src/foundry/client/pixi/groups/overlay.d.mts
@@ -4,16 +4,24 @@ declare global {
   /**
    * A container group which is not bound to the stage world transform.
    */
-  class OverlayCanvasGroup extends CanvasGroupMixin<typeof UnboundContainer, "overlay">(UnboundContainer) {
+  class OverlayCanvasGroup<
+    DrawOptions extends OverlayCanvasGroup.DrawOptions = OverlayCanvasGroup.DrawOptions,
+    TearDownOptions extends OverlayCanvasGroup.TearDownOptions = OverlayCanvasGroup.TearDownOptions,
+  > extends CanvasGroupMixin<typeof UnboundContainer, "overlay">(UnboundContainer)<DrawOptions, TearDownOptions> {
     /**
+     * Should this group tear down its non-layer children?
      * @defaultValue `false`
      */
     static override tearDownChildren: boolean;
   }
 
   namespace OverlayCanvasGroup {
-    type Any = AnyOverlayCanvasGroup;
+    interface Any extends AnyOverlayCanvasGroup {}
     type AnyConstructor = typeof AnyOverlayCanvasGroup;
+
+    interface DrawOptions extends CanvasGroupMixin.DrawOptions {}
+
+    interface TearDownOptions extends CanvasGroupMixin.TearDownOptions {}
   }
 }
 
diff --git a/src/foundry/client/pixi/groups/primary.d.mts b/src/foundry/client/pixi/groups/primary.d.mts
index 9aefe7e4f..121e2ee21 100644
--- a/src/foundry/client/pixi/groups/primary.d.mts
+++ b/src/foundry/client/pixi/groups/primary.d.mts
@@ -1,4 +1,4 @@
-import type { Renderer } from "pixi.js";
+import type { Brand, HandleEmptyObject } from "fvtt-types/utils";
 
 // Included to match Foundry's documented types
 type PrimaryCanvasObject = ReturnType<typeof PrimaryCanvasObjectMixin>;
@@ -9,7 +9,10 @@ declare global {
    * This group is a {@link CachedContainer} which is rendered to the Scene as a {@link SpriteMesh}.
    * This allows the rendered result of the Primary Canvas Group to be affected by a {@link BaseSamplerShader}.
    */
-  class PrimaryCanvasGroup extends CanvasGroupMixin<typeof CachedContainer, "primary">(CachedContainer) {
+  class PrimaryCanvasGroup<
+    DrawOptions extends PrimaryCanvasGroup.DrawOptions = PrimaryCanvasGroup.DrawOptions,
+    TearDownOptions extends PrimaryCanvasGroup.TearDownOptions = PrimaryCanvasGroup.TearDownOptions,
+  > extends CanvasGroupMixin<typeof CachedContainer, "primary">(CachedContainer)<DrawOptions, TearDownOptions> {
     /**
      * @param sprite - (default: `new SpriteMesh(undefined, BaseSamplerShader)`)
      */
@@ -18,19 +21,19 @@ declare global {
     /**
      * Sort order to break ties on the group/layer level.
      */
-    static readonly SORT_LAYERS: {
-      SCENE: number;
-      TILES: number;
-      DRAWINGS: number;
-      TOKENS: number;
-      WEATHER: number;
-    };
+    static SORT_LAYERS: PrimaryCanvasGroup.Sort_Layers;
 
-    static override textureConfiguration: {
-      scaleMode: PIXI.SCALE_MODES;
-      format: PIXI.FORMATS;
-      multisample: PIXI.MSAA_QUALITY;
-    };
+    /**
+     * @defaultValue
+     * ```js
+     * {
+     *   scaleMode: PIXI.SCALE_MODES.NEAREST,
+     *   format: PIXI.FORMATS.RGB,
+     *   multisample: PIXI.MSAA_QUALITY.NONE
+     * }
+     * ```
+     */
+    static override textureConfiguration: CachedContainer.TextureConfiguration;
 
     /**
      * @defaultValue `"none"`
@@ -40,12 +43,12 @@ declare global {
     /**
      * @defaultValue `[0, 0, 0, 0]`
      */
-    override clearColor: [r: number, g: number, b: number, a: number];
+    override clearColor: Color.RGBAColorVector;
 
     /**
      * The background color in RGB.
      */
-    _backgroundColor?: [red: number, green: number, blue: number];
+    _backgroundColor: Color.RGBColorVector | undefined;
 
     /**
      * Track the set of HTMLVideoElements which are currently playing as part of this group.
@@ -54,6 +57,7 @@ declare global {
 
     /**
      * Occludable objects above this elevation are faded on hover.
+     * @defaultValue `0`
      */
     hoverFadeElevation: number;
 
@@ -66,53 +70,52 @@ declare global {
     /**
      * The primary background image configured for the Scene, rendered as a SpriteMesh.
      */
-    background: SpriteMesh;
+    background: SpriteMesh | undefined;
 
     /**
      * The primary foreground image configured for the Scene, rendered as a SpriteMesh.
      */
-    foreground: SpriteMesh;
+    foreground: SpriteMesh | undefined;
 
     /**
      * A Quadtree which partitions and organizes primary canvas objects.
      */
-    quadtree: CanvasQuadtree<PrimaryCanvasObjectMixin.Any>;
+    quadtree: CanvasQuadtree<PrimaryCanvasObjectMixin.AnyMixed>;
 
     /**
      * The collection of PrimaryDrawingContainer objects which are rendered in the Scene.
+     * @privateRemarks Foundry types this as `Collection<PrimaryDrawingContainer>`, which doesn't exist. It's `PrimaryGraphics` in practice.
      */
     drawings: Collection<PrimaryGraphics>;
 
     /**
      * The collection of SpriteMesh objects which are rendered in the Scene.
+     * @privateRemarks Foundry types this as `Collection<TokenMesh>`, which doesn't exist. In practice it's `PrimarySpriteMesh`
      */
     tokens: Collection<PrimarySpriteMesh>;
 
     /**
      * The collection of SpriteMesh objects which are rendered in the Scene.
+     * @privateRemarks Foundry types this as `Collection<PrimarySpriteMesh | TileSprite>`, but `TileSprite` doens't exist. In practice it's all `PrimarySpriteMesh`.
      */
     tiles: Collection<PrimarySpriteMesh>;
 
     /**
      * The ambience filter which is applying post-processing effects.
      */
-    _ambienceFilter: PrimaryCanvasGroupAmbienceFilter;
-
-    /**
-     * Render all tokens in their own render texture.
-     * @param renderer - The renderer to use.
-     */
-    _renderTokens(renderer: PIXI.Renderer): void;
+    _ambienceFilter: PrimaryCanvasGroupAmbienceFilter | undefined;
 
     /**
      * Return the base HTML image or video element which provides the background texture.
+     * @privateRemarks Foundry does not indicate the possibility of a null return
      */
-    get backgroundSource(): HTMLImageElement | HTMLVideoElement;
+    get backgroundSource(): HTMLImageElement | HTMLVideoElement | null;
 
     /**
      * Return the base HTML image or video element which provides the foreground texture.
+     * @privateRemarks Foundry does not indicate the possibility of a null return
      */
-    get foregroundSource(): HTMLImageElement | HTMLVideoElement;
+    get foregroundSource(): HTMLImageElement | HTMLVideoElement | null;
 
     /**
      * Refresh the primary mesh.
@@ -124,61 +127,62 @@ declare global {
      */
     update(): void;
 
-    protected override _draw(options: CanvasGroupMixin.DrawOptions): Promise<void>;
+    protected override _draw(options: HandleEmptyObject<DrawOptions>): Promise<void>;
 
-    protected override _render(_renderer: Renderer): void;
+    protected override _render(_renderer: PIXI.Renderer): void;
 
-    protected override _tearDown(options: CanvasGroupMixin.TearDownOptions): Promise<void>;
+    protected override _tearDown(options: HandleEmptyObject<TearDownOptions>): Promise<void>;
 
     /**
      * Draw the SpriteMesh for a specific Token object.
      * @param token - The Token being added
      * @returns The added PrimarySpriteMesh
      */
-    addToken(token: Token): PrimarySpriteMesh;
+    addToken(token: Token.ConfiguredInstance): PrimarySpriteMesh;
 
     /**
      * Remove a TokenMesh from the group.
      * @param token - The Token being removed
      */
-    removeToken(token: Token): void;
+    removeToken(token: Token.ConfiguredInstance): void;
 
     /**
      * Draw the SpriteMesh for a specific Token object.
      * @param tile - The Tile being added
      * @returns The added PrimarySpriteMesh
      */
-    addTile(tile: Tile): PrimarySpriteMesh;
+    addTile(tile: Tile.ConfiguredInstance): PrimarySpriteMesh;
 
     /**
      * Remove a TokenMesh from the group.
      * @param tile - The Tile being removed
      */
-    removeTile(tile: Tile): void;
+    removeTile(tile: Tile.ConfiguredInstance): void;
 
     /**
      * Add a PrimaryGraphics to the group.
      * @param drawing - The Drawing being added
      * @returns The created PrimaryGraphics instance
      */
-    addDrawing(drawing: Drawing): PrimaryGraphics;
+    addDrawing(drawing: Drawing.ConfiguredInstance): PrimaryGraphics;
 
     /**
      * Remove a PrimaryGraphics from the group.
      * @param drawing - The Drawing being removed
      */
-    removeDrawing(drawing: Drawing): void;
+    removeDrawing(drawing: Drawing.ConfiguredInstance): void;
 
     /**
      * Override the default PIXI.Container behavior for how objects in this container are sorted.
-     * @override
+     * @remarks Actually an override of `PIXI.Container#sortChildren`
      */
     sortChildren(): void;
 
     /**
      * Handle mousemove events on the primary group to update the hovered state of its children.
+     * @remarks Foundry marked `@internal`
      */
-    protected _onMouseMove(): void;
+    _onMouseMove(): void;
 
     /**
      * @deprecated since v12, will be removed in v14
@@ -194,8 +198,22 @@ declare global {
   }
 
   namespace PrimaryCanvasGroup {
-    type Any = AnyPrimaryCanvasGroup;
+    interface Any extends AnyPrimaryCanvasGroup {}
     type AnyConstructor = typeof AnyPrimaryCanvasGroup;
+
+    interface DrawOptions extends CanvasGroupMixin.DrawOptions {}
+
+    interface TearDownOptions extends CanvasGroupMixin.TearDownOptions {}
+
+    type SORT_LAYERS = Brand<number, "PrimaryCanvasGroup.SORT_LAYERS">;
+
+    interface Sort_Layers {
+      readonly SCENE: 0 & SORT_LAYERS;
+      readonly TILES: 500 & SORT_LAYERS;
+      readonly DRAWINGS: 600 & SORT_LAYERS;
+      readonly TOKENS: 700 & SORT_LAYERS;
+      readonly WEATHER: 1000 & SORT_LAYERS;
+    }
   }
 }
 
diff --git a/src/foundry/client/pixi/groups/rendered.d.mts b/src/foundry/client/pixi/groups/rendered.d.mts
index 9eface47a..bb94b0956 100644
--- a/src/foundry/client/pixi/groups/rendered.d.mts
+++ b/src/foundry/client/pixi/groups/rendered.d.mts
@@ -4,16 +4,24 @@ declare global {
   /**
    * A container group which contains the environment canvas group and the interface canvas group.
    */
-  class RenderedCanvasGroup extends CanvasGroupMixin<typeof PIXI.Container, "rendered">(PIXI.Container) {
+  class RenderedCanvasGroup<
+    DrawOptions extends RenderedCanvasGroup.DrawOptions = RenderedCanvasGroup.DrawOptions,
+    TearDownOptions extends RenderedCanvasGroup.TearDownOptions = RenderedCanvasGroup.TearDownOptions,
+  > extends CanvasGroupMixin<typeof PIXI.Container, "rendered">(PIXI.Container)<DrawOptions, TearDownOptions> {
     /**
+     * Should this group tear down its non-layer children?
      * @defaultValue `false`
      */
     static override tearDownChildren: boolean;
   }
 
   namespace RenderedCanvasGroup {
-    type Any = AnyRenderedCanvasGroup;
+    interface Any extends AnyRenderedCanvasGroup {}
     type AnyConstructor = typeof AnyRenderedCanvasGroup;
+
+    interface DrawOptions extends CanvasGroupMixin.DrawOptions {}
+
+    interface TearDownOptions extends CanvasGroupMixin.TearDownOptions {}
   }
 }
 
diff --git a/src/foundry/client/pixi/layers/effects/visibility.d.mts b/src/foundry/client/pixi/layers/effects/visibility.d.mts
index bd9b8b059..3165867f3 100644
--- a/src/foundry/client/pixi/layers/effects/visibility.d.mts
+++ b/src/foundry/client/pixi/layers/effects/visibility.d.mts
@@ -185,6 +185,7 @@ declare global {
   }
 
   namespace CanvasVisibility {
+    interface Any extends AnyCanvasVisibility {}
     type AnyConstructor = typeof AnyCanvasVisibility;
 
     type LightingVisibility = ValueOf<typeof VisionMode.LIGHTING_VISIBILITY>;
diff --git a/src/foundry/client/pixi/placeables/primary-canvas-objects/primary-canvas-object.d.mts b/src/foundry/client/pixi/placeables/primary-canvas-objects/primary-canvas-object.d.mts
index e188b96e7..53f9dacab 100644
--- a/src/foundry/client/pixi/placeables/primary-canvas-objects/primary-canvas-object.d.mts
+++ b/src/foundry/client/pixi/placeables/primary-canvas-objects/primary-canvas-object.d.mts
@@ -19,7 +19,7 @@ declare class PrimaryCanvasObject {
    * or a `Token`, `Tile`, or the `PrimaryCanvasGroup` (via `PrimarySpriteMesh`), or its default value `null`
    */
   //TODO: (esheyw) Revisit the "any canvas group" type when groups are done
-  object: PlaceableObject.Any | CanvasGroupMixin.Any | null;
+  object: PlaceableObject.Any | CanvasGroupMixin.AnyMixed | null;
 
   /**
    * The elevation of this object.
@@ -173,9 +173,6 @@ declare global {
   ): Mixin<typeof PrimaryCanvasObject, ReturnType<typeof CanvasTransformMixin<BaseClass>>>;
 
   namespace PrimaryCanvasObjectMixin {
-    interface Any extends AnyPrimaryCanvasObject {}
-    type AnyConstructor = typeof AnyPrimaryCanvasObject;
-
     type AnyMixedConstructor = ReturnType<typeof PrimaryCanvasObjectMixin<PrimaryCanvasObjectMixin.BaseClass>>;
     interface AnyMixed extends AnyMixedConstructor {}
 
@@ -193,19 +190,9 @@ declare global {
   ): Mixin<typeof CanvasTransformMixinClass, BaseClass>;
 
   namespace CanvasTransformMixin {
-    interface Any extends AnyCanvasTransformMixinClass {}
-    type AnyConstructor = typeof AnyCanvasTransformMixinClass;
-
     type AnyMixedConstructor = ReturnType<typeof CanvasTransformMixin<CanvasTransformMixin.BaseClass>>;
     interface AnyMixed extends AnyMixedConstructor {}
 
     type BaseClass = PIXI.Container.AnyConstructor;
   }
 }
-
-declare abstract class AnyPrimaryCanvasObject extends PrimaryCanvasObject {
-  constructor(arg0: never, ...args: never[]);
-}
-declare abstract class AnyCanvasTransformMixinClass extends CanvasTransformMixinClass {
-  constructor(arg0: never, ...args: never[]);
-}
diff --git a/src/foundry/client/pixi/placeables/primary-canvas-objects/primary-sprite-mesh.d.mts b/src/foundry/client/pixi/placeables/primary-canvas-objects/primary-sprite-mesh.d.mts
index e2b32eb76..ce958dfe9 100644
--- a/src/foundry/client/pixi/placeables/primary-canvas-objects/primary-sprite-mesh.d.mts
+++ b/src/foundry/client/pixi/placeables/primary-canvas-objects/primary-sprite-mesh.d.mts
@@ -155,7 +155,7 @@ declare global {
        * @privateRemarks Foundry types as `*`, but the only things passed in practice are `Tile`s, `Token`s, and the `PrimaryCanvasGroup`
        */
       //TODO: (esheyw) Revisit the "any canvas group" type when groups are done
-      object: PlaceableObject.Any | CanvasGroupMixin.Any;
+      object: PlaceableObject.Any | CanvasGroupMixin.AnyMixed;
     }>;
 
     /** The constructor options */


Test patch:
diff --git a/tests/foundry/client/pixi/core/containers/base-canvas-group.test-d.ts b/tests/foundry/client/pixi/core/containers/base-canvas-group.test-d.ts
index 6143419e0..e2cd096d1 100644
--- a/tests/foundry/client/pixi/core/containers/base-canvas-group.test-d.ts
+++ b/tests/foundry/client/pixi/core/containers/base-canvas-group.test-d.ts
@@ -1,7 +1,17 @@
-import { expectTypeOf } from "vitest";
+/* eslint-disable @typescript-eslint/no-namespace */
+import { assertType, expectTypeOf } from "vitest";
 
-class MyGroupClass extends BaseCanvasMixin(PIXI.Container) {}
+class FakePrimaryGroup<
+  DrawOptions extends PrimaryCanvasGroup.DrawOptions = PrimaryCanvasGroup.DrawOptions,
+  TearDownOptions extends PrimaryCanvasGroup.TearDownOptions = PrimaryCanvasGroup.TearDownOptions,
+> extends CanvasGroupMixin<typeof PIXI.Container, "primary">(PIXI.Container)<DrawOptions, TearDownOptions> {}
 
-const MyGroup = new MyGroupClass();
+expectTypeOf(FakePrimaryGroup.groupName).toMatchTypeOf<keyof CONFIG.Canvas.Groups>();
+expectTypeOf(FakePrimaryGroup.tearDownChildren).toEqualTypeOf<boolean>();
 
-expectTypeOf(MyGroup.layers["grid"]).toEqualTypeOf<CanvasLayer>();
+const MyGroup = new FakePrimaryGroup();
+assertType<PIXI.Container>(MyGroup);
+
+expectTypeOf(MyGroup.layers).toEqualTypeOf<CanvasGroupMixin.LayersFor<"primary">>();
+expectTypeOf(MyGroup.name).toEqualTypeOf<string>();
+expectTypeOf(MyGroup.draw()).toEqualTypeOf<Promise<FakePrimaryGroup>>();
diff --git a/tests/foundry/client/pixi/core/containers/unbound-container.test-d.ts b/tests/foundry/client/pixi/core/containers/unbound-container.test-d.ts
index ff6d81105..12a454f94 100644
--- a/tests/foundry/client/pixi/core/containers/unbound-container.test-d.ts
+++ b/tests/foundry/client/pixi/core/containers/unbound-container.test-d.ts
@@ -1,1 +1,6 @@
-// Intentionally left blank and tested in /client/pixi/groups/overlay
+import { expectTypeOf } from "vitest";
+
+expectTypeOf(UnboundTransform.IDENTITY).toEqualTypeOf<UnboundTransform>();
+
+// Some further testing in tests/foundry/client/pixi/groups/overlay.test-d.ts,
+// as OverlayCanvasGroup is the only class extending UnboundContainer in core
diff --git a/tests/foundry/client/pixi/groups/effects.test-d.ts b/tests/foundry/client/pixi/groups/effects.test-d.ts
index 203cca957..1b6a98f0b 100644
--- a/tests/foundry/client/pixi/groups/effects.test-d.ts
+++ b/tests/foundry/client/pixi/groups/effects.test-d.ts
@@ -1,5 +1,22 @@
 import { expectTypeOf } from "vitest";
 
+expectTypeOf(EffectsCanvasGroup.groupName).toEqualTypeOf<undefined>();
+
+declare const somePoint: PIXI.Point;
 const myEffectGroup = new EffectsCanvasGroup();
 
+expectTypeOf(myEffectGroup.layers).toEqualTypeOf<CanvasGroupMixin.LayersFor<"effects">>();
+expectTypeOf(
+  myEffectGroup.activatePostProcessingFilters(VisualEffectsMaskingFilter.FILTER_MODES.BACKGROUND, ["CONTRAST"], {
+    someUniformKey: [1, 2, 3],
+  }),
+).toEqualTypeOf<void>();
+expectTypeOf(myEffectGroup.testInsideLight(somePoint, 0)).toEqualTypeOf<boolean>();
+expectTypeOf(myEffectGroup.testInsideDarkness(somePoint)).toEqualTypeOf<boolean>();
+expectTypeOf(myEffectGroup.getDarknessLevel(somePoint, 20)).toEqualTypeOf<number>();
+expectTypeOf(myEffectGroup.toggleMaskingFilters(false)).toEqualTypeOf<void>();
 expectTypeOf(myEffectGroup.animateDarkness(3, { duration: 40000 })).toEqualTypeOf<Promise<boolean | void>>();
+
+//deprecated since v12 until v14
+expectTypeOf(myEffectGroup.visibility).toMatchTypeOf<CanvasVisibility.Any>();
+expectTypeOf(myEffectGroup.globalLightSource).toMatchTypeOf<foundry.canvas.sources.GlobalLightSource.Any>();
diff --git a/tests/foundry/client/pixi/groups/environment.test-d.ts b/tests/foundry/client/pixi/groups/environment.test-d.ts
index b6a567509..23c91512d 100644
--- a/tests/foundry/client/pixi/groups/environment.test-d.ts
+++ b/tests/foundry/client/pixi/groups/environment.test-d.ts
@@ -1,7 +1,27 @@
 import { expectTypeOf } from "vitest";
 
-expectTypeOf(EnvironmentCanvasGroup.groupName).toEqualTypeOf<string>();
+expectTypeOf(EnvironmentCanvasGroup.groupName).toEqualTypeOf<"environment">();
+expectTypeOf(EnvironmentCanvasGroup.tearDownChildren).toEqualTypeOf<boolean>();
 
 const myEnvironmentGroup = new EnvironmentCanvasGroup();
+expectTypeOf(
+  myEnvironmentGroup.initialize({
+    backgroundColor: [0.2, 0.4, 0.8],
+    brightestColor: "#FFFFFF",
+    daylightColor: "AABBCC",
+    fogExploredColor: null,
+    fogUnexploredColor: undefined,
+    environment: {
+      //TODO: expand test once SceneEnvironmentData is properly typed (dependant on BaseScene schema)
+    },
+    darknessLevel: 5, //deprecated since v12 until v14
+  }),
+).toEqualTypeOf<void>();
+expectTypeOf(myEnvironmentGroup.layers).toEqualTypeOf<CanvasGroupMixin.LayersFor<"environment">>();
+expectTypeOf(myEnvironmentGroup.globalLightSource).toMatchTypeOf<foundry.canvas.sources.GlobalLightSource.Any>();
+expectTypeOf(myEnvironmentGroup.darknessLevel).toEqualTypeOf<number>();
+expectTypeOf(myEnvironmentGroup.colors.ambientBrightest).toEqualTypeOf<Color | undefined>();
+expectTypeOf(myEnvironmentGroup.weights.bright).toEqualTypeOf<number | undefined>();
 
-expectTypeOf(myEnvironmentGroup.layers).toEqualTypeOf<Record<string, CanvasLayer>>();
+//deprecated since v12 until v14
+expectTypeOf(myEnvironmentGroup.darknessPenalty).toEqualTypeOf<0>();
diff --git a/tests/foundry/client/pixi/groups/hidden.test-d.ts b/tests/foundry/client/pixi/groups/hidden.test-d.ts
index ea72a58d3..68ce41c02 100644
--- a/tests/foundry/client/pixi/groups/hidden.test-d.ts
+++ b/tests/foundry/client/pixi/groups/hidden.test-d.ts
@@ -1,11 +1,11 @@
 import { expectTypeOf } from "vitest";
 
-expectTypeOf(HiddenCanvasGroup.groupName).toEqualTypeOf<string>();
+expectTypeOf(HiddenCanvasGroup.groupName).toEqualTypeOf<"hidden">();
 
+declare const someDisplayObject: PIXI.DisplayObject;
 const myHiddenGroup = new HiddenCanvasGroup();
 
-expectTypeOf(myHiddenGroup.layers).toEqualTypeOf<Record<string, CanvasLayer>>();
-
-const myDisplayObject = new PIXI.LegacyGraphics();
-
-expectTypeOf(myHiddenGroup.addMask("foobar", myDisplayObject)).toEqualTypeOf<void>();
+expectTypeOf(myHiddenGroup.layers).toEqualTypeOf<CanvasGroupMixin.LayersFor<"hidden">>();
+expectTypeOf(myHiddenGroup.masks).toEqualTypeOf<PIXI.Container>();
+expectTypeOf(myHiddenGroup.invalidateMasks()).toEqualTypeOf<void>();
+expectTypeOf(myHiddenGroup.addMask("foobar", someDisplayObject, 2)).toEqualTypeOf<void>();
diff --git a/tests/foundry/client/pixi/groups/interface.test-d.ts b/tests/foundry/client/pixi/groups/interface.test-d.ts
index 459721667..e4990ae41 100644
--- a/tests/foundry/client/pixi/groups/interface.test-d.ts
+++ b/tests/foundry/client/pixi/groups/interface.test-d.ts
@@ -1,11 +1,23 @@
 import { expectTypeOf } from "vitest";
 
-expectTypeOf(InterfaceCanvasGroup.groupName).toEqualTypeOf<string>();
+expectTypeOf(InterfaceCanvasGroup.groupName).toEqualTypeOf<"interface">();
 
+declare const someDrawing: Drawing.ConfiguredInstance;
 const myInterfaceGroup = new InterfaceCanvasGroup();
 
-expectTypeOf(myInterfaceGroup.layers).toEqualTypeOf<Record<string, CanvasLayer>>();
-
-expectTypeOf(myInterfaceGroup.createScrollingText({ x: 0, y: 0 }, "foobar")).toEqualTypeOf<
-  Promise<PreciseText | null>
->();
+expectTypeOf(myInterfaceGroup.layers).toEqualTypeOf<CanvasGroupMixin.LayersFor<"interface">>();
+expectTypeOf(myInterfaceGroup.addDrawing(someDrawing)).toEqualTypeOf<PIXI.Graphics>();
+expectTypeOf(myInterfaceGroup.removeDrawing(someDrawing)).toEqualTypeOf<void>();
+expectTypeOf(
+  myInterfaceGroup.createScrollingText({ x: 0, y: 0 }, "Scrolling! Text!", {
+    distance: 256,
+    anchor: CONST.TEXT_ANCHOR_POINTS.RIGHT,
+    direction: CONST.TEXT_ANCHOR_POINTS.LEFT,
+    jitter: 0.2,
+    duration: 3000,
+    // past this line are `PIXI.ITextStyle` props
+    dropShadow: true,
+    fontFamily: "serif",
+    strokeThickness: 4,
+  }),
+).toEqualTypeOf<Promise<PreciseText | null>>();
diff --git a/tests/foundry/client/pixi/groups/overlay.test-d.ts b/tests/foundry/client/pixi/groups/overlay.test-d.ts
index 750b0c9a7..6150dfaec 100644
--- a/tests/foundry/client/pixi/groups/overlay.test-d.ts
+++ b/tests/foundry/client/pixi/groups/overlay.test-d.ts
@@ -1,10 +1,10 @@
 import { expectTypeOf } from "vitest";
 
-expectTypeOf(OverlayCanvasGroup.groupName).toEqualTypeOf<string>();
+expectTypeOf(OverlayCanvasGroup.groupName).toEqualTypeOf<"overlay">();
 
 const myOverlayGroup = new OverlayCanvasGroup();
 
-expectTypeOf(myOverlayGroup.layers).toEqualTypeOf<Record<string, CanvasLayer>>();
+expectTypeOf(myOverlayGroup.layers).toEqualTypeOf<CanvasGroupMixin.LayersFor<"overlay">>();
 
 const myTransform = new PIXI.Transform();
 
diff --git a/tests/foundry/client/pixi/groups/primary.test-d.ts b/tests/foundry/client/pixi/groups/primary.test-d.ts
index 932c6a109..d224449ed 100644
--- a/tests/foundry/client/pixi/groups/primary.test-d.ts
+++ b/tests/foundry/client/pixi/groups/primary.test-d.ts
@@ -1,9 +1,24 @@
 import { expectTypeOf } from "vitest";
 
-expectTypeOf(PrimaryCanvasGroup.groupName).toEqualTypeOf<string>();
+expectTypeOf(PrimaryCanvasGroup.groupName).toMatchTypeOf<keyof CONFIG.Canvas.Groups>();
+expectTypeOf(PrimaryCanvasGroup.tearDownChildren).toEqualTypeOf<boolean>();
+expectTypeOf(PrimaryCanvasGroup.BACKGROUND_ELEVATION).toEqualTypeOf<number>();
 
-const myMesh = new SpriteMesh();
+declare const someMesh: SpriteMesh;
+declare const someToken: Token.ConfiguredInstance;
+declare const someTile: Tile.ConfiguredInstance;
+declare const someDrawing: Drawing.ConfiguredInstance;
+const myPrimaryGroup = new PrimaryCanvasGroup(someMesh);
 
-const myPrimaryGroup = new PrimaryCanvasGroup(myMesh);
+expectTypeOf(myPrimaryGroup.layers).toEqualTypeOf<CanvasGroupMixin.LayersFor<"primary">>();
 
-expectTypeOf(myPrimaryGroup.layers).toEqualTypeOf<Record<string, CanvasLayer>>();
+expectTypeOf(myPrimaryGroup._ambienceFilter).toEqualTypeOf<PrimaryCanvasGroupAmbienceFilter | undefined>();
+
+expectTypeOf(myPrimaryGroup.addToken(someToken)).toEqualTypeOf<PrimarySpriteMesh>();
+expectTypeOf(myPrimaryGroup.addTile(someTile)).toEqualTypeOf<PrimarySpriteMesh>();
+expectTypeOf(myPrimaryGroup.addDrawing(someDrawing)).toEqualTypeOf<PrimaryGraphics>();
+
+//deprecated since v11, until v13
+expectTypeOf(myPrimaryGroup.mapElevationAlpha(20)).toEqualTypeOf<number>();
+//depcated since v12, until v14
+expectTypeOf(myPrimaryGroup.mapElevationToDepth(20)).toEqualTypeOf<number>();
diff --git a/tests/foundry/client/pixi/groups/renderered.test-d.ts b/tests/foundry/client/pixi/groups/renderered.test-d.ts
index 09515367a..94a1f878c 100644
--- a/tests/foundry/client/pixi/groups/renderered.test-d.ts
+++ b/tests/foundry/client/pixi/groups/renderered.test-d.ts
@@ -1,7 +1,7 @@
 import { expectTypeOf } from "vitest";
 
-expectTypeOf(RenderedCanvasGroup.groupName).toEqualTypeOf<string>();
+expectTypeOf(RenderedCanvasGroup.groupName).toEqualTypeOf<"rendered">();
 
 const myRenderedGroup = new RenderedCanvasGroup();
 
-expectTypeOf(myRenderedGroup.layers).toEqualTypeOf<Record<string, CanvasLayer>>();
+expectTypeOf(myRenderedGroup.layers).toEqualTypeOf<CanvasGroupMixin.LayersFor<"rendered">>();