Repository: playcanvas/engine
Pull request #6356 (2024-05-13 10:36:39)

Remove handling of WebGL1 seamless cubemap sampling
WebGL2/WebGPU support this by default


Patch:
diff --git a/src/deprecated/deprecated.js b/src/deprecated/deprecated.js
index 95d601edf..910c0d805 100644
--- a/src/deprecated/deprecated.js
+++ b/src/deprecated/deprecated.js
@@ -126,9 +126,6 @@ import { basisInitialize } from '../framework/handlers/basis.js';
 import { LitShader } from '../scene/shader-lib/programs/lit-shader.js';
 import { Geometry } from '../scene/geometry/geometry.js';
 
-// ScriptType alias
-export { Script as ScriptType } from '../framework/script/script.js';
-
 // CORE
 export const LINEBATCH_WORLD = 0;
 export const LINEBATCH_OVERLAY = 1;
diff --git a/src/framework/components/script/component.js b/src/framework/components/script/component.js
index cc918e721..c182759bf 100644
--- a/src/framework/components/script/component.js
+++ b/src/framework/components/script/component.js
@@ -19,7 +19,7 @@ import { Entity } from '../../entity.js';
  */
 class ScriptComponent extends Component {
     /**
-     * Fired when a {@link Script} instance is created and attached to the script component.
+     * Fired when a {@link ScriptType} instance is created and attached to the script component.
      * This event is available in two forms. They are as follows:
      *
      * 1. `create` - Fired when a script instance is created. The name of the script type and the
@@ -40,7 +40,7 @@ class ScriptComponent extends Component {
     static EVENT_CREATE = 'create';
 
     /**
-     * Fired when a {@link Script} instance is destroyed and removed from the script component.
+     * Fired when a {@link ScriptType} instance is destroyed and removed from the script component.
      * This event is available in two forms. They are as follows:
      *
      * 1. `destroy` - Fired when a script instance is destroyed. The name of the script type and
@@ -109,7 +109,7 @@ class ScriptComponent extends Component {
     static EVENT_STATE = 'state';
 
     /**
-     * Fired when the index of a {@link Script} instance is changed in the script component.
+     * Fired when the index of a {@link ScriptType} instance is changed in the script component.
      * This event is available in two forms. They are as follows:
      *
      * 1. `move` - Fired when a script instance is moved. The name of the script type, the script
@@ -130,7 +130,7 @@ class ScriptComponent extends Component {
     static EVENT_MOVE = 'move';
 
     /**
-     * Fired when a {@link Script} instance had an exception. The handler is passed the script
+     * Fired when a {@link ScriptType} instance had an exception. The handler is passed the script
      * instance, the exception and the method name that the exception originated from.
      *
      * @event
@@ -154,7 +154,7 @@ class ScriptComponent extends Component {
         /**
          * Holds all script instances for this component.
          *
-         * @type {import('../../script/script.js').Script[]}
+         * @type {import('../../script/script-type.js').ScriptType[]}
          * @private
          */
         this._scripts = [];
@@ -191,7 +191,7 @@ class ScriptComponent extends Component {
      * An array of all script instances attached to an entity. This array is read-only and should
      * not be modified by developer.
      *
-     * @type {import('../../script/script.js').Script[]}
+     * @type {import('../../script/script-type.js').ScriptType[]}
      */
     set scripts(value) {
         this._scriptsData = value;
@@ -546,8 +546,8 @@ class ScriptComponent extends Component {
     /**
      * Detect if script is attached to an entity.
      *
-     * @param {string|typeof import('../../script/script.js').Script} nameOrType - The
-     * name or type of {@link Script}.
+     * @param {string|typeof import('../../script/script-type.js').ScriptType} nameOrType - The
+     * name or type of {@link ScriptType}.
      * @returns {boolean} If script is attached to an entity.
      * @example
      * if (entity.script.has('playerController')) {
@@ -570,9 +570,9 @@ class ScriptComponent extends Component {
     /**
      * Get a script instance (if attached).
      *
-     * @param {string|typeof import('../../script/script.js').Script} nameOrType - The
-     * name or type of {@link Script}.
-     * @returns {import('../../script/script.js').Script|null} If script is attached, the
+     * @param {string|typeof import('../../script/script-type.js').ScriptType} nameOrType - The
+     * name or type of {@link ScriptType}.
+     * @returns {import('../../script/script-type.js').ScriptType|null} If script is attached, the
      * instance is returned. Otherwise null is returned.
      * @example
      * const controller = entity.script.get('playerController');
@@ -594,8 +594,8 @@ class ScriptComponent extends Component {
     /**
      * Create a script instance and attach to an entity script component.
      *
-     * @param {string|typeof import('../../script/script.js').Script} nameOrType - The
-     * name or type of {@link Script}.
+     * @param {string|typeof import('../../script/script-type.js').ScriptType} nameOrType - The
+     * name or type of {@link ScriptType}.
      * @param {object} [args] - Object with arguments for a script.
      * @param {boolean} [args.enabled] - If script instance is enabled after creation. Defaults to
      * true.
@@ -605,9 +605,9 @@ class ScriptComponent extends Component {
      * script and attributes must be initialized manually. Defaults to false.
      * @param {number} [args.ind] - The index where to insert the script instance at. Defaults to
      * -1, which means append it at the end.
-     * @returns {import('../../script/script.js').Script|null} Returns an instance of a
-     * {@link Script} if successfully attached to an entity, or null if it failed because a
-     * script with a same name has already been added or if the {@link Script} cannot be found
+     * @returns {import('../../script/script-type.js').ScriptType|null} Returns an instance of a
+     * {@link ScriptType} if successfully attached to an entity, or null if it failed because a
+     * script with a same name has already been added or if the {@link ScriptType} cannot be found
      * by name in the {@link ScriptRegistry}.
      * @example
      * entity.script.create('playerController', {
@@ -699,8 +699,8 @@ class ScriptComponent extends Component {
     /**
      * Destroy the script instance that is attached to an entity.
      *
-     * @param {string|typeof import('../../script/script.js').Script} nameOrType - The
-     * name or type of {@link Script}.
+     * @param {string|typeof import('../../script/script-type.js').ScriptType} nameOrType - The
+     * name or type of {@link ScriptType}.
      * @returns {boolean} If it was successfully destroyed.
      * @example
      * entity.script.destroy('playerController');
@@ -756,8 +756,8 @@ class ScriptComponent extends Component {
     /**
      * Swap the script instance.
      *
-     * @param {string|typeof import('../../script/script.js').Script} nameOrType - The
-     * name or type of {@link Script}.
+     * @param {string|typeof import('../../script/script-type.js').ScriptType} nameOrType - The
+     * name or type of {@link ScriptType}.
      * @returns {boolean} If it was successfully swapped.
      * @private
      */
@@ -927,8 +927,8 @@ class ScriptComponent extends Component {
     /**
      * Move script instance to different position to alter update order of scripts within entity.
      *
-     * @param {string|typeof import('../../script/script.js').Script} nameOrType - The
-     * name or type of {@link Script}.
+     * @param {string|typeof import('../../script/script-type.js').ScriptType} nameOrType - The
+     * name or type of {@link ScriptType}.
      * @param {number} ind - New position index.
      * @returns {boolean} If it was successfully moved.
      * @example
diff --git a/src/framework/entity.js b/src/framework/entity.js
index e4ca2cb4b..08b808127 100644
--- a/src/framework/entity.js
+++ b/src/framework/entity.js
@@ -400,8 +400,8 @@ class Entity extends GraphNode {
     /**
      * Search the entity and all of its descendants for the first script instance of specified type.
      *
-     * @param {string|typeof import('./script/script.js').Script} nameOrType - The name or type of {@link Script}.
-     * @returns {import('./script/script.js').Script|undefined} A script instance of specified type, if the entity or any of its descendants
+     * @param {string|typeof import('./script/script-type.js').ScriptType} nameOrType - The name or type of {@link ScriptType}.
+     * @returns {import('./script/script-type.js').ScriptType|undefined} A script instance of specified type, if the entity or any of its descendants
      * has one. Returns undefined otherwise.
      * @example
      * // Get the first found "playerController" instance in the hierarchy tree that starts with this entity
@@ -415,8 +415,8 @@ class Entity extends GraphNode {
     /**
      * Search the entity and all of its descendants for all script instances of specified type.
      *
-     * @param {string|typeof import('./script/script.js').Script} nameOrType - The name or type of {@link Script}.
-     * @returns {import('./script/script.js').Script[]} All script instances of specified type in the entity or any of its
+     * @param {string|typeof import('./script/script-type.js').ScriptType} nameOrType - The name or type of {@link ScriptType}.
+     * @returns {import('./script/script-type.js').ScriptType[]} All script instances of specified type in the entity or any of its
      * descendants. Returns empty array if none found.
      * @example
      * // Get all "playerController" instances in the hierarchy tree that starts with this entity
diff --git a/src/framework/handlers/cubemap.js b/src/framework/handlers/cubemap.js
index 9978f4842..bfd6a15aa 100644
--- a/src/framework/handlers/cubemap.js
+++ b/src/framework/handlers/cubemap.js
@@ -121,7 +121,6 @@ class CubemapHandler extends ResourceHandler {
                             height: tex.height >> i,
                             format: tex.format,
                             levels: [tex._levels[i]],
-                            fixCubemapSeams: true,
                             addressU: ADDRESS_CLAMP_TO_EDGE,
                             addressV: ADDRESS_CLAMP_TO_EDGE,
                             // generate cubemaps on the top level only
@@ -180,8 +179,7 @@ class CubemapHandler extends ResourceHandler {
                     magFilter: assetData.hasOwnProperty('magFilter') ? assetData.magFilter : faceTextures[0].magFilter,
                     anisotropy: assetData.hasOwnProperty('anisotropy') ? assetData.anisotropy : 1,
                     addressU: ADDRESS_CLAMP_TO_EDGE,
-                    addressV: ADDRESS_CLAMP_TO_EDGE,
-                    fixCubemapSeams: !!assets[0]
+                    addressV: ADDRESS_CLAMP_TO_EDGE
                 });
 
                 resources[0] = faces;
diff --git a/src/framework/handlers/script.js b/src/framework/handlers/script.js
index c63caab4e..d77f0f71f 100644
--- a/src/framework/handlers/script.js
+++ b/src/framework/handlers/script.js
@@ -1,8 +1,8 @@
 import { platform } from '../../core/platform.js';
 import { script } from '../script.js';
-import { Script } from '../script/script.js';
+import { ScriptType } from '../script/script-type.js';
 import { ScriptTypes } from '../script/script-types.js';
-import { registerScript } from '../script/script-create.js';
+import { registerScript } from '../script/script.js';
 import { ResourceLoader } from './loader.js';
 
 import { ResourceHandler } from './handler.js';
@@ -154,7 +154,7 @@ class ScriptHandler extends ResourceHandler {
 
             for (const key in module) {
                 const scriptClass = module[key];
-                const extendsScriptType = scriptClass.prototype instanceof Script;
+                const extendsScriptType = scriptClass.prototype instanceof ScriptType;
 
                 if (extendsScriptType) {
 
diff --git a/src/framework/script/script-attributes.js b/src/framework/script/script-attributes.js
index 7a718b401..0d0dc1d90 100644
--- a/src/framework/script/script-attributes.js
+++ b/src/framework/script/script-attributes.js
@@ -150,8 +150,8 @@ function rawToValue(app, args, value, old) {
 
 /**
  * Container of Script Attribute definitions. Implements an interface to add/remove attributes and
- * store their definition for a {@link Script}. Note: An instance of ScriptAttributes is
- * created automatically by each {@link Script}.
+ * store their definition for a {@link ScriptType}. Note: An instance of ScriptAttributes is
+ * created automatically by each {@link ScriptType}.
  *
  * @category Script
  */
@@ -159,7 +159,7 @@ class ScriptAttributes {
     /**
      * Create a new ScriptAttributes instance.
      *
-     * @param {typeof import('./script.js').Script} scriptType - Script Type that attributes relate to.
+     * @param {typeof import('./script-type.js').ScriptType} scriptType - Script Type that attributes relate to.
      */
     constructor(scriptType) {
         this.scriptType = scriptType;
diff --git a/src/framework/script/script-create.js b/src/framework/script/script-create.js
deleted file mode 100644
index b463156e2..000000000
--- a/src/framework/script/script-create.js
+++ /dev/null
@@ -1,148 +0,0 @@
-import { Debug } from '../../core/debug.js';
-
-import { script } from '../script.js';
-import { AppBase } from '../app-base.js';
-
-import { ScriptAttributes } from './script-attributes.js';
-import { Script } from './script.js';
-import { ScriptTypes } from './script-types.js';
-
-const reservedScriptNames = new Set([
-    'system', 'entity', 'create', 'destroy', 'swap', 'move', 'data',
-    'scripts', '_scripts', '_scriptsIndex', '_scriptsData',
-    'enabled', '_oldState', 'onEnable', 'onDisable', 'onPostStateChange',
-    '_onSetEnabled', '_checkState', '_onBeforeRemove',
-    '_onInitializeAttributes', '_onInitialize', '_onPostInitialize',
-    '_onUpdate', '_onPostUpdate',
-    '_callbacks', '_callbackActive', 'has', 'get', 'on', 'off', 'fire', 'once', 'hasEvent'
-]);
-
-function getReservedScriptNames() {
-    return reservedScriptNames;
-}
-
-/**
- * Create and register a new {@link Script}. It returns new class type (constructor function),
- * which is auto-registered to {@link ScriptRegistry} using its name. This is the main interface to
- * create Script Types, to define custom logic using JavaScript, that is used to create interaction
- * for entities.
- *
- * @param {string} name - Unique Name of a Script Type. If a Script Type with the same name has
- * already been registered and the new one has a `swap` method defined in its prototype, then it
- * will perform hot swapping of existing Script Instances on entities using this new Script Type.
- * Note: There is a reserved list of names that cannot be used, such as list below as well as some
- * starting from `_` (underscore): system, entity, create, destroy, swap, move, scripts, onEnable,
- * onDisable, onPostStateChange, has, on, off, fire, once, hasEvent.
- * @param {AppBase} [app] - Optional application handler, to choose which {@link ScriptRegistry}
- * to add a script to. By default it will use `Application.getApplication()` to get current
- * {@link AppBase}.
- * @returns {typeof Script|null} A class type (constructor function) that inherits {@link Script},
- * which the developer is meant to further extend by adding attributes and prototype methods.
- * Returns null if there was an error.
- * @example
- * var Turning = pc.createScript('turn');
- *
- * // define 'speed' attribute that is available in Editor UI
- * Turning.attributes.add('speed', {
- *     type: 'number',
- *     default: 180,
- *     placeholder: 'deg/s'
- * });
- *
- * // runs every tick
- * Turning.prototype.update = function (dt) {
- *     this.entity.rotate(0, this.speed * dt, 0);
- * };
- * @category Script
- */
-function createScript(name, app) {
-    if (script.legacy) {
-        Debug.error('This project is using the legacy script system. You cannot call pc.createScript().');
-        return null;
-    }
-
-    if (reservedScriptNames.has(name))
-        throw new Error(`Script name '${name}' is reserved, please rename the script`);
-
-    class ScriptWithAttributes extends Script {
-        attributes = new ScriptAttributes(ScriptWithAttributes);
-    }
-
-    registerScript(ScriptWithAttributes, name, app);
-    return ScriptWithAttributes;
-}
-
-// Editor uses this - migrate to ScriptAttributes.reservedNames and delete this
-const reservedAttributes = {};
-ScriptAttributes.reservedNames.forEach((value, value2, set) => {
-    reservedAttributes[value] = 1;
-});
-createScript.reservedAttributes = reservedAttributes;
-
-/* eslint-disable jsdoc/check-examples */
-/**
- * Register a existing class type as a Script Type to {@link ScriptRegistry}. Useful when defining
- * a ES6 script class that extends {@link Script} (see example).
- *
- * @param {typeof Script} script - The existing class type (constructor function) to be
- * registered as a Script Type. Class must extend {@link Script} (see example). Please note: A
- * class created using {@link createScript} is auto-registered, and should therefore not be pass
- * into {@link registerScript} (which would result in swapping out all related script instances).
- * @param {string} [name] - Optional unique name of the Script Type. By default it will use the
- * same name as the existing class. If a Script Type with the same name has already been registered
- * and the new one has a `swap` method defined in its prototype, then it will perform hot swapping
- * of existing Script Instances on entities using this new Script Type. Note: There is a reserved
- * list of names that cannot be used, such as list below as well as some starting from `_`
- * (underscore): system, entity, create, destroy, swap, move, scripts, onEnable, onDisable,
- * onPostStateChange, has, on, off, fire, once, hasEvent.
- * @param {AppBase} [app] - Optional application handler, to choose which {@link ScriptRegistry}
- * to register the script type to. By default it will use `Application.getApplication()` to get
- * current {@link AppBase}.
- * @example
- * // define a ES6 script class
- * class PlayerController extends pc.Script {
- *
- *     initialize() {
- *         // called once on initialize
- *     }
- *
- *     update(dt) {
- *         // called each tick
- *     }
- * }
- *
- * // register the class as a script
- * pc.registerScript(PlayerController);
- *
- * // declare script attributes (Must be after pc.registerScript())
- * PlayerController.attributes.add('attribute1', {type: 'number'});
- * @category Script
- */
-function registerScript(script, name, app) {
-    if (script.legacy) {
-        Debug.error('This project is using the legacy script system. You cannot call pc.registerScript().');
-        return;
-    }
-
-    if (typeof script !== 'function')
-        throw new Error(`script class: '${script}' must be a constructor function (i.e. class).`);
-
-    if (!(script.prototype instanceof Script))
-        throw new Error(`script class: '${Script.__getScriptName(script)}' does not extend pc.Script.`);
-
-    name = name || script.__name || Script.__getScriptName(script);
-
-    if (reservedScriptNames.has(name))
-        throw new Error(`script name: '${name}' is reserved, please change script name`);
-
-    script.__name = name;
-
-    // add to scripts registry
-    const registry = app ? app.scripts : AppBase.getApplication().scripts;
-    registry.add(script);
-
-    ScriptTypes.push(script, script.legacy);
-}
-/* eslint-enable jsdoc/check-examples */
-
-export { createScript, registerScript, getReservedScriptNames };
diff --git a/src/framework/script/script-registry.js b/src/framework/script/script-registry.js
index 78db4b531..12f08201c 100644
--- a/src/framework/script/script-registry.js
+++ b/src/framework/script/script-registry.js
@@ -1,7 +1,7 @@
 import { EventHandler } from '../../core/event-handler.js';
 
 /**
- * Container for all {@link Script}s that are available to this application. Note that
+ * Container for all {@link ScriptType}s that are available to this application. Note that
  * PlayCanvas scripts can access the Script Registry from inside the application with
  * {@link AppBase#scripts}.
  *
@@ -9,13 +9,13 @@ import { EventHandler } from '../../core/event-handler.js';
  */
 class ScriptRegistry extends EventHandler {
     /**
-     * @type {Object<string, typeof import('./script.js').Script>}
+     * @type {Object<string, typeof import('./script-type.js').ScriptType>}
      * @private
      */
     _scripts = {};
 
     /**
-     * @type {typeof import('./script.js').Script[]}
+     * @type {typeof import('./script-type.js').ScriptType[]}
      * @private
      */
     _list = [];
@@ -37,12 +37,12 @@ class ScriptRegistry extends EventHandler {
     }
 
     /**
-     * Add {@link Script} to registry. Note: when {@link createScript} is called, it will add
-     * the {@link Script} to the registry automatically. If a script already exists in
+     * Add {@link ScriptType} to registry. Note: when {@link createScript} is called, it will add
+     * the {@link ScriptType} to the registry automatically. If a script already exists in
      * registry, and the new script has a `swap` method defined, it will perform code hot swapping
      * automatically in async manner.
      *
-     * @param {typeof import('./script.js').Script} script - Script Type that is created
+     * @param {typeof import('./script-type.js').ScriptType} script - Script Type that is created
      * using {@link createScript}.
      * @returns {boolean} True if added for the first time or false if script already exists.
      * @example
@@ -146,10 +146,10 @@ class ScriptRegistry extends EventHandler {
     }
 
     /**
-     * Remove {@link Script}.
+     * Remove {@link ScriptType}.
      *
-     * @param {string|typeof import('./script.js').Script} nameOrType - The name or type
-     * of {@link Script}.
+     * @param {string|typeof import('./script-type.js').ScriptType} nameOrType - The name or type
+     * of {@link ScriptType}.
      * @returns {boolean} True if removed or False if already not in registry.
      * @example
      * app.scripts.remove('playerController');
@@ -178,10 +178,10 @@ class ScriptRegistry extends EventHandler {
     }
 
     /**
-     * Get {@link Script} by name.
+     * Get {@link ScriptType} by name.
      *
-     * @param {string} name - Name of a {@link Script}.
-     * @returns {typeof import('./script.js').Script} The Script Type if it exists in the
+     * @param {string} name - Name of a {@link ScriptType}.
+     * @returns {typeof import('./script-type.js').ScriptType} The Script Type if it exists in the
      * registry or null otherwise.
      * @example
      * var PlayerController = app.scripts.get('playerController');
@@ -191,11 +191,11 @@ class ScriptRegistry extends EventHandler {
     }
 
     /**
-     * Check if a {@link Script} with the specified name is in the registry.
+     * Check if a {@link ScriptType} with the specified name is in the registry.
      *
-     * @param {string|typeof import('./script.js').Script} nameOrType - The name or type
-     * of {@link Script}.
-     * @returns {boolean} True if {@link Script} is in registry.
+     * @param {string|typeof import('./script-type.js').ScriptType} nameOrType - The name or type
+     * of {@link ScriptType}.
+     * @returns {boolean} True if {@link ScriptType} is in registry.
      * @example
      * if (app.scripts.has('playerController')) {
      *     // playerController is in pc.ScriptRegistry
@@ -212,9 +212,9 @@ class ScriptRegistry extends EventHandler {
     }
 
     /**
-     * Get list of all {@link Script}s from registry.
+     * Get list of all {@link ScriptType}s from registry.
      *
-     * @returns {Array<typeof import('./script.js').Script>} list of all {@link Script}s
+     * @returns {Array<typeof import('./script-type.js').ScriptType>} list of all {@link ScriptType}s
      * in registry.
      * @example
      * // logs array of all Script Type names available in registry
diff --git a/src/framework/script/script-type.js b/src/framework/script/script-type.js
new file mode 100644
index 000000000..3a1ff14a3
--- /dev/null
+++ b/src/framework/script/script-type.js
@@ -0,0 +1,393 @@
+import { Debug } from '../../core/debug.js';
+import { EventHandler } from '../../core/event-handler.js';
+
+import { SCRIPT_INITIALIZE, SCRIPT_POST_INITIALIZE } from './constants.js';
+import { ScriptAttributes } from './script-attributes.js';
+
+const funcNameRegex = new RegExp('^\\s*function(?:\\s|\\s*\\/\\*.*\\*\\/\\s*)+([^\\(\\s\\/]*)\\s*');
+
+/**
+ * Represents the type of a script. It is returned by {@link createScript}. Also referred to as
+ * Script Type.
+ *
+ * The type is to be extended using its JavaScript prototype. There is a list of methods that will
+ * be executed by the engine on instances of this type, such as:
+ *
+ * - `initialize`
+ * - `postInitialize`
+ * - `update`
+ * - `postUpdate`
+ * - `swap`
+ *
+ * `initialize` and `postInitialize` - are called (if defined) when a script is about to run for
+ * the first time - `postInitialize` will run after all `initialize` methods are executed in the
+ * same tick or enabling chain of actions.
+ *
+ * `update` and `postUpdate` - are called (if defined) for enabled (running state) scripts on each
+ * tick.
+ *
+ * `swap` - is called when a ScriptType that already exists in the registry gets redefined. If the
+ * new ScriptType has a `swap` method in its prototype, then it will be executed to perform hot-
+ * reload at runtime.
+ *
+ * @category Script
+ */
+class ScriptType extends EventHandler {
+    /**
+     * Fired when a script instance becomes enabled.
+     *
+     * @event
+     * @example
+     * PlayerController.prototype.initialize = function () {
+     *     this.on('enable', () => {
+     *         // Script Instance is now enabled
+     *     });
+     * };
+     */
+    static EVENT_ENABLE = 'enable';
+
+    /**
+     * Fired when a script instance becomes disabled.
+     *
+     * @event
+     * @example
+     * PlayerController.prototype.initialize = function () {
+     *     this.on('disable', () => {
+     *         // Script Instance is now disabled
+     *     });
+     * };
+     */
+    static EVENT_DISABLE = 'disable';
+
+    /**
+     * Fired when a script instance changes state to enabled or disabled. The handler is passed a
+     * boolean parameter that states whether the script instance is now enabled or disabled.
+     *
+     * @event
+     * @example
+     * PlayerController.prototype.initialize = function () {
+     *     this.on('state', (enabled) => {
+     *         console.log(`Script Instance is now ${enabled ? 'enabled' : 'disabled'}`);
+     *     });
+     * };
+     */
+    static EVENT_STATE = 'state';
+
+    /**
+     * Fired when a script instance is destroyed and removed from component.
+     *
+     * @event
+     * @example
+     * PlayerController.prototype.initialize = function () {
+     *     this.on('destroy', () => {
+     *         // no longer part of the entity
+     *         // this is a good place to clean up allocated resources used by the script
+     *     });
+     * };
+     */
+    static EVENT_DESTROY = 'destroy';
+
+    /**
+     * Fired when script attributes have changed. This event is available in two forms. They are as follows:
+     *
+     * 1. `attr` - Fired for any attribute change. The handler is passed the name of the attribute
+     * that changed, the value of the attribute before the change and the value of the attribute
+     * after the change.
+     * 2. `attr:[name]` - Fired for a specific attribute change. The handler is passed the value of
+     * the attribute before the change and the value of the attribute after the change.
+     *
+     * @event
+     * @example
+     * PlayerController.prototype.initialize = function () {
+     *     this.on('attr', (name, newValue, oldValue) => {
+     *         console.log(`Attribute '${name}' changed from '${oldValue}' to '${newValue}'`);
+     *     });
+     * };
+     * @example
+     * PlayerController.prototype.initialize = function () {
+     *     this.on('attr:speed', (newValue, oldValue) => {
+     *         console.log(`Attribute 'speed' changed from '${oldValue}' to '${newValue}'`);
+     *     });
+     * };
+     */
+    static EVENT_ATTR = 'attr';
+
+    /**
+     * Fired when a script instance had an exception. The script instance will be automatically
+     * disabled. The handler is passed an {@link Error} object containing the details of the
+     * exception and the name of the method that threw the exception.
+     *
+     * @event
+     * @example
+     * PlayerController.prototype.initialize = function () {
+     *     this.on('error', (err, method) => {
+     *         // caught an exception
+     *         console.log(err.stack);
+     *     });
+     * };
+     */
+    static EVENT_ERROR = 'error';
+
+    /**
+     * The {@link AppBase} that the instance of this type belongs to.
+     *
+     * @type {import('../app-base.js').AppBase}
+     */
+    app;
+
+    /**
+     * The {@link Entity} that the instance of this type belongs to.
+     *
+     * @type {import('../entity.js').Entity}
+     */
+    entity;
+
+    /** @private */
+    _enabled;
+
+    /** @private */
+    _enabledOld;
+
+    /** @private */
+    _initialized;
+
+    /** @private */
+    _postInitialized;
+
+    /** @private */
+    __destroyed;
+
+    /** @private */
+    __attributes;
+
+    /** @private */
+    __attributesRaw;
+
+    /** @private */
+    __scriptType;
+
+    /**
+     * The order in the script component that the methods of this script instance will run
+     * relative to other script instances in the component.
+     *
+     * @type {number}
+     * @private
+     */
+    __executionOrder;
+
+    /**
+     * Create a new ScriptType instance.
+     *
+     * @param {object} args - The input arguments object.
+     * @param {import('../app-base.js').AppBase} args.app - The {@link AppBase} that is running the
+     * script.
+     * @param {import('../entity.js').Entity} args.entity - The {@link Entity} that the script is
+     * attached to.
+     */
+    constructor(args) {
+        super();
+        this.initScriptType(args);
+    }
+
+    /**
+     * True if the instance of this type is in running state. False when script is not running,
+     * because the Entity or any of its parents are disabled or the {@link ScriptComponent} is
+     * disabled or the Script Instance is disabled. When disabled no update methods will be called
+     * on each tick. initialize and postInitialize methods will run once when the script instance
+     * is in `enabled` state during app tick.
+     *
+     * @type {boolean}
+     */
+    set enabled(value) {
+        this._enabled = !!value;
+
+        if (this.enabled === this._enabledOld) return;
+
+        this._enabledOld = this.enabled;
+        this.fire(this.enabled ? 'enable' : 'disable');
+        this.fire('state', this.enabled);
+
+        // initialize script if not initialized yet and script is enabled
+        if (!this._initialized && this.enabled) {
+            this._initialized = true;
+
+            this.__initializeAttributes(true);
+
+            if (this.initialize)
+                this.entity.script._scriptMethod(this, SCRIPT_INITIALIZE);
+        }
+
+        // post initialize script if not post initialized yet and still enabled
+        // (initialize might have disabled the script so check this.enabled again)
+        // Warning: Do not do this if the script component is currently being enabled
+        // because in this case post initialize must be called after all the scripts
+        // in the script component have been initialized first
+        if (this._initialized && !this._postInitialized && this.enabled && !this.entity.script._beingEnabled) {
+            this._postInitialized = true;
+
+            if (this.postInitialize)
+                this.entity.script._scriptMethod(this, SCRIPT_POST_INITIALIZE);
+        }
+    }
+
+    get enabled() {
+        return this._enabled && !this._destroyed && this.entity.script.enabled && this.entity.enabled;
+    }
+
+    /**
+     * @param {{entity: import('../entity.js').Entity, app: import('../app-base.js').AppBase}} args -
+     * The entity and app.
+     * @private
+     */
+    initScriptType(args) {
+        const script = this.constructor; // get script type, i.e. function (class)
+        Debug.assert(args && args.app && args.entity, `script [${script.__name}] has missing arguments in constructor`);
+
+        this.app = args.app;
+        this.entity = args.entity;
+
+        this._enabled = typeof args.enabled === 'boolean' ? args.enabled : true;
+        this._enabledOld = this.enabled;
+
+        this.__destroyed = false;
+        this.__attributes = { };
+        this.__attributesRaw = args.attributes || { }; // need at least an empty object to make sure default attributes are initialized
+        this.__scriptType = script;
+        this.__executionOrder = -1;
+    }
+
+    /**
+     * Name of a Script Type.
+     *
+     * @type {string}
+     * @private
+     */
+    static __name = null; // Will be assigned when calling createScript or registerScript.
+
+    /**
+     * @param {*} constructorFn - The constructor function of the script type.
+     * @returns {string} The script name.
+     * @private
+     */
+    static __getScriptName(constructorFn) {
+        if (typeof constructorFn !== 'function') return undefined;
+        if ('name' in Function.prototype) return constructorFn.name;
+        if (constructorFn === Function || constructorFn === Function.prototype.constructor) return 'Function';
+        const match = ('' + constructorFn).match(funcNameRegex);
+        return match ? match[1] : undefined;
+    }
+
+    /**
+     * Name of a Script Type.
+     *
+     * @type {string|null}
+     */
+    static get scriptName() {
+        return this.__name;
+    }
+
+    /**
+     * The interface to define attributes for Script Types. Refer to {@link ScriptAttributes}.
+     *
+     * @type {ScriptAttributes}
+     * @example
+     * var PlayerController = pc.createScript('playerController');
+     *
+     * PlayerController.attributes.add('speed', {
+     *     type: 'number',
+     *     title: 'Speed',
+     *     placeholder: 'km/h',
+     *     default: 22.2
+     * });
+     */
+    static get attributes() {
+        if (!this.hasOwnProperty('__attributes')) this.__attributes = new ScriptAttributes(this);
+        return this.__attributes;
+    }
+
+    /**
+     * @param {boolean} [force] - Set to true to force initialization of the attributes.
+     * @private
+     */
+    __initializeAttributes(force) {
+        if (!force && !this.__attributesRaw)
+            return;
+
+        // set attributes values
+        for (const key in this.__scriptType.attributes.index) {
+            if (this.__attributesRaw && this.__attributesRaw.hasOwnProperty(key)) {
+                this[key] = this.__attributesRaw[key];
+            } else if (!this.__attributes.hasOwnProperty(key)) {
+                if (this.__scriptType.attributes.index[key].hasOwnProperty('default')) {
+                    this[key] = this.__scriptType.attributes.index[key].default;
+                } else {
+                    this[key] = null;
+                }
+            }
+        }
+
+        this.__attributesRaw = null;
+    }
+
+    /**
+     * Shorthand function to extend Script Type prototype with list of methods.
+     *
+     * @param {object} methods - Object with methods, where key - is name of method, and value - is function.
+     * @example
+     * var PlayerController = pc.createScript('playerController');
+     *
+     * PlayerController.extend({
+     *     initialize: function () {
+     *         // called once on initialize
+     *     },
+     *     update: function (dt) {
+     *         // called each tick
+     *     }
+     * });
+     */
+    static extend(methods) {
+        for (const key in methods) {
+            if (!methods.hasOwnProperty(key))
+                continue;
+
+            this.prototype[key] = methods[key];
+        }
+    }
+
+    /**
+     * @function
+     * @name ScriptType#[initialize]
+     * @description Called when script is about to run for the first time.
+     */
+
+    /**
+     * @function
+     * @name ScriptType#[postInitialize]
+     * @description Called after all initialize methods are executed in the same tick or enabling chain of actions.
+     */
+
+    /**
+     * @function
+     * @name ScriptType#[update]
+     * @description Called for enabled (running state) scripts on each tick.
+     * @param {number} dt - The delta time in seconds since the last frame.
+     */
+
+    /**
+     * @function
+     * @name ScriptType#[postUpdate]
+     * @description Called for enabled (running state) scripts on each tick, after update.
+     * @param {number} dt - The delta time in seconds since the last frame.
+     */
+
+    /**
+     * @function
+     * @name ScriptType#[swap]
+     * @description Called when a ScriptType that already exists in the registry
+     * gets redefined. If the new ScriptType has a `swap` method in its prototype,
+     * then it will be executed to perform hot-reload at runtime.
+     * @param {ScriptType} old - Old instance of the scriptType to copy data to the new instance.
+     */
+}
+
+export { ScriptType };
diff --git a/src/framework/script/script.js b/src/framework/script/script.js
index a136619f1..b25939d38 100644
--- a/src/framework/script/script.js
+++ b/src/framework/script/script.js
@@ -1,404 +1,156 @@
 import { Debug } from '../../core/debug.js';
 import { EventHandler } from '../../core/event-handler.js';
 
-import { SCRIPT_INITIALIZE, SCRIPT_POST_INITIALIZE } from './constants.js';
-import { ScriptAttributes } from './script-attributes.js';
+import { script } from '../script.js';
+import { AppBase } from '../app-base.js';
 
-const funcNameRegex = new RegExp('^\\s*function(?:\\s|\\s*\\/\\*.*\\*\\/\\s*)+([^\\(\\s\\/]*)\\s*');
+import { ScriptAttributes } from './script-attributes.js';
+import { ScriptType } from './script-type.js';
+import { ScriptTypes } from './script-types.js';
+
+const reservedScriptNames = new Set([
+    'system', 'entity', 'create', 'destroy', 'swap', 'move', 'data',
+    'scripts', '_scripts', '_scriptsIndex', '_scriptsData',
+    'enabled', '_oldState', 'onEnable', 'onDisable', 'onPostStateChange',
+    '_onSetEnabled', '_checkState', '_onBeforeRemove',
+    '_onInitializeAttributes', '_onInitialize', '_onPostInitialize',
+    '_onUpdate', '_onPostUpdate',
+    '_callbacks', '_callbackActive', 'has', 'get', 'on', 'off', 'fire', 'once', 'hasEvent'
+]);
+
+function getReservedScriptNames() {
+    return reservedScriptNames;
+}
 
 /**
- * The Script class is a base class that you must extend to receive
- * {@link https://developer.playcanvas.com/user-manual/scripting/anatomy/ various lifecycle updates}
- * from the engine.
- *
- * You can create a Script using either {@link createScript} or by extending the class directly.
- *
- * ```javascript
- * import { Script } from 'playcanvas';
- * class Rotator extends Script {
- *     update() {
- *         this.entity.rotate(0, 0.1, 0);
- *     }
- * }
- * ```
- * The following methods are called if they exist on the Script instance:
- *
- * - `initialize`
- * - `postInitialize`
- * - `update`
- * - `postUpdate`
- * - `swap`
+ * Create and register a new {@link ScriptType}. It returns new class type (constructor function),
+ * which is auto-registered to {@link ScriptRegistry} using its name. This is the main interface to
+ * create Script Types, to define custom logic using JavaScript, that is used to create interaction
+ * for entities.
  *
- * `initialize` and `postInitialize` - are called (if defined) when a script is about to run for
- * the first time - `postInitialize` will run after all `initialize` methods are executed in the
- * same tick or enabling chain of actions.
+ * @param {string} name - Unique Name of a Script Type. If a Script Type with the same name has
+ * already been registered and the new one has a `swap` method defined in its prototype, then it
+ * will perform hot swapping of existing Script Instances on entities using this new Script Type.
+ * Note: There is a reserved list of names that cannot be used, such as list below as well as some
+ * starting from `_` (underscore): system, entity, create, destroy, swap, move, scripts, onEnable,
+ * onDisable, onPostStateChange, has, on, off, fire, once, hasEvent.
+ * @param {AppBase} [app] - Optional application handler, to choose which {@link ScriptRegistry}
+ * to add a script to. By default it will use `Application.getApplication()` to get current
+ * {@link AppBase}.
+ * @returns {typeof ScriptType|null} A class type (constructor function) that inherits {@link ScriptType},
+ * which the developer is meant to further extend by adding attributes and prototype methods.
+ * Returns null if there was an error.
+ * @example
+ * var Turning = pc.createScript('turn');
  *
- * `update` and `postUpdate` - are called (if defined) for enabled (running state) scripts on each
- * tick.
+ * // define 'speed' attribute that is available in Editor UI
+ * Turning.attributes.add('speed', {
+ *     type: 'number',
+ *     default: 180,
+ *     placeholder: 'deg/s'
+ * });
  *
- * `swap` - is called when a Script that already exists in the registry gets redefined. If the
- * new Script has a `swap` method in its prototype, then it will be executed to perform hot-
- * reload at runtime.
- *
- * @see {@link https://developer.playcanvas.com/user-manual/scripting/anatomy/} for more information.
+ * // runs every tick
+ * Turning.prototype.update = function (dt) {
+ *     this.entity.rotate(0, this.speed * dt, 0);
+ * };
  * @category Script
  */
-class Script extends EventHandler {
-    /**
-     * Fired when a script instance becomes enabled.
-     *
-     * @event
-     * @example
-     * PlayerController.prototype.initialize = function () {
-     *     this.on('enable', () => {
-     *         // Script Instance is now enabled
-     *     });
-     * };
-     */
-    static EVENT_ENABLE = 'enable';
-
-    /**
-     * Fired when a script instance becomes disabled.
-     *
-     * @event
-     * @example
-     * PlayerController.prototype.initialize = function () {
-     *     this.on('disable', () => {
-     *         // Script Instance is now disabled
-     *     });
-     * };
-     */
-    static EVENT_DISABLE = 'disable';
-
-    /**
-     * Fired when a script instance changes state to enabled or disabled. The handler is passed a
-     * boolean parameter that states whether the script instance is now enabled or disabled.
-     *
-     * @event
-     * @example
-     * PlayerController.prototype.initialize = function () {
-     *     this.on('state', (enabled) => {
-     *         console.log(`Script Instance is now ${enabled ? 'enabled' : 'disabled'}`);
-     *     });
-     * };
-     */
-    static EVENT_STATE = 'state';
-
-    /**
-     * Fired when a script instance is destroyed and removed from component.
-     *
-     * @event
-     * @example
-     * PlayerController.prototype.initialize = function () {
-     *     this.on('destroy', () => {
-     *         // no longer part of the entity
-     *         // this is a good place to clean up allocated resources used by the script
-     *     });
-     * };
-     */
-    static EVENT_DESTROY = 'destroy';
-
-    /**
-     * Fired when script attributes have changed. This event is available in two forms. They are as follows:
-     *
-     * 1. `attr` - Fired for any attribute change. The handler is passed the name of the attribute
-     * that changed, the value of the attribute before the change and the value of the attribute
-     * after the change.
-     * 2. `attr:[name]` - Fired for a specific attribute change. The handler is passed the value of
-     * the attribute before the change and the value of the attribute after the change.
-     *
-     * @event
-     * @example
-     * PlayerController.prototype.initialize = function () {
-     *     this.on('attr', (name, newValue, oldValue) => {
-     *         console.log(`Attribute '${name}' changed from '${oldValue}' to '${newValue}'`);
-     *     });
-     * };
-     * @example
-     * PlayerController.prototype.initialize = function () {
-     *     this.on('attr:speed', (newValue, oldValue) => {
-     *         console.log(`Attribute 'speed' changed from '${oldValue}' to '${newValue}'`);
-     *     });
-     * };
-     */
-    static EVENT_ATTR = 'attr';
-
-    /**
-     * Fired when a script instance had an exception. The script instance will be automatically
-     * disabled. The handler is passed an {@link Error} object containing the details of the
-     * exception and the name of the method that threw the exception.
-     *
-     * @event
-     * @example
-     * PlayerController.prototype.initialize = function () {
-     *     this.on('error', (err, method) => {
-     *         // caught an exception
-     *         console.log(err.stack);
-     *     });
-     * };
-     */
-    static EVENT_ERROR = 'error';
-
-    /**
-     * The {@link AppBase} that the instance of this type belongs to.
-     *
-     * @type {import('../app-base.js').AppBase}
-     */
-    app;
-
-    /**
-     * The {@link Entity} that the instance of this type belongs to.
-     *
-     * @type {import('../entity.js').Entity}
-     */
-    entity;
-
-    /** @private */
-    _enabled;
-
-    /** @private */
-    _enabledOld;
-
-    /** @private */
-    _initialized;
-
-    /** @private */
-    _postInitialized;
-
-    /** @private */
-    __destroyed;
-
-    /** @private */
-    __attributes;
-
-    /** @private */
-    __attributesRaw;
-
-    /** @private */
-    __scriptType;
-
-    /**
-     * The order in the script component that the methods of this script instance will run
-     * relative to other script instances in the component.
-     *
-     * @type {number}
-     * @private
-     */
-    __executionOrder;
-
-    /**
-     * Create a new Script instance.
-     *
-     * @param {object} args - The input arguments object.
-     * @param {import('../app-base.js').AppBase} args.app - The {@link AppBase} that is running the
-     * script.
-     * @param {import('../entity.js').Entity} args.entity - The {@link Entity} that the script is
-     * attached to.
-     */
-    constructor(args) {
-        super();
-        this.initScriptType(args);
-    }
-
-    /**
-     * True if the instance of this type is in running state. False when script is not running,
-     * because the Entity or any of its parents are disabled or the {@link ScriptComponent} is
-     * disabled or the Script Instance is disabled. When disabled no update methods will be called
-     * on each tick. initialize and postInitialize methods will run once when the script instance
-     * is in `enabled` state during app tick.
-     *
-     * @type {boolean}
-     */
-    set enabled(value) {
-        this._enabled = !!value;
-
-        if (this.enabled === this._enabledOld) return;
-
-        this._enabledOld = this.enabled;
-        this.fire(this.enabled ? 'enable' : 'disable');
-        this.fire('state', this.enabled);
-
-        // initialize script if not initialized yet and script is enabled
-        if (!this._initialized && this.enabled) {
-            this._initialized = true;
-
-            this.__initializeAttributes(true);
-
-            if (this.initialize)
-                this.entity.script._scriptMethod(this, SCRIPT_INITIALIZE);
-        }
-
-        // post initialize script if not post initialized yet and still enabled
-        // (initialize might have disabled the script so check this.enabled again)
-        // Warning: Do not do this if the script component is currently being enabled
-        // because in this case post initialize must be called after all the scripts
-        // in the script component have been initialized first
-        if (this._initialized && !this._postInitialized && this.enabled && !this.entity.script._beingEnabled) {
-            this._postInitialized = true;
-
-            if (this.postInitialize)
-                this.entity.script._scriptMethod(this, SCRIPT_POST_INITIALIZE);
-        }
+function createScript(name, app) {
+    if (script.legacy) {
+        Debug.error('This project is using the legacy script system. You cannot call pc.createScript().');
+        return null;
     }
 
-    get enabled() {
-        return this._enabled && !this._destroyed && this.entity.script.enabled && this.entity.enabled;
-    }
+    if (reservedScriptNames.has(name))
+        throw new Error(`Script name '${name}' is reserved, please rename the script`);
 
-    /**
-     * @param {{entity: import('../entity.js').Entity, app: import('../app-base.js').AppBase}} args -
-     * The entity and app.
-     * @private
-     */
-    initScriptType(args) {
-        const script = this.constructor; // get script type, i.e. function (class)
-        Debug.assert(args && args.app && args.entity, `script [${script.__name}] has missing arguments in constructor`);
+    const scriptType = function (args) {
+        EventHandler.prototype.initEventHandler.call(this);
+        ScriptType.prototype.initScriptType.call(this, args);
+    };
 
-        this.app = args.app;
-        this.entity = args.entity;
+    scriptType.prototype = Object.create(ScriptType.prototype);
+    scriptType.prototype.constructor = scriptType;
 
-        this._enabled = typeof args.enabled === 'boolean' ? args.enabled : true;
-        this._enabledOld = this.enabled;
+    scriptType.extend = ScriptType.extend;
+    scriptType.attributes = new ScriptAttributes(scriptType);
 
-        this.__destroyed = false;
-        this.__attributes = { };
-        this.__attributesRaw = args.attributes || { }; // need at least an empty object to make sure default attributes are initialized
-        this.__scriptType = script;
-        this.__executionOrder = -1;
-    }
-
-    /**
-     * Name of a Script.
-     *
-     * @type {string}
-     * @private
-     */
-    static __name = null; // Will be assigned when calling createScript or registerScript.
-
-    /**
-     * @param {*} constructorFn - The constructor function of the script type.
-     * @returns {string} The script name.
-     * @private
-     */
-    static __getScriptName(constructorFn) {
-        if (typeof constructorFn !== 'function') return undefined;
-        if ('name' in Function.prototype) return constructorFn.name;
-        if (constructorFn === Function || constructorFn === Function.prototype.constructor) return 'Function';
-        const match = ('' + constructorFn).match(funcNameRegex);
-        return match ? match[1] : undefined;
-    }
-
-    /**
-     * Name of a Script Type.
-     *
-     * @type {string|null}
-     */
-    static get scriptName() {
-        return this.__name;
-    }
-
-    /**
-     * The interface to define attributes for Script Types. Refer to {@link ScriptAttributes}.
-     *
-     * @type {ScriptAttributes}
-     * @example
-     * var PlayerController = pc.createScript('playerController');
-     *
-     * PlayerController.attributes.add('speed', {
-     *     type: 'number',
-     *     title: 'Speed',
-     *     placeholder: 'km/h',
-     *     default: 22.2
-     * });
-     */
-    static get attributes() {
-        if (!this.hasOwnProperty('__attributes')) this.__attributes = new ScriptAttributes(this);
-        return this.__attributes;
-    }
-
-    /**
-     * @param {boolean} [force] - Set to true to force initialization of the attributes.
-     * @private
-     */
-    __initializeAttributes(force) {
-        if (!force && !this.__attributesRaw)
-            return;
+    registerScript(scriptType, name, app);
+    return scriptType;
+}
 
-        // set attributes values
-        for (const key in this.__scriptType.attributes.index) {
-            if (this.__attributesRaw && this.__attributesRaw.hasOwnProperty(key)) {
-                this[key] = this.__attributesRaw[key];
-            } else if (!this.__attributes.hasOwnProperty(key)) {
-                if (this.__scriptType.attributes.index[key].hasOwnProperty('default')) {
-                    this[key] = this.__scriptType.attributes.index[key].default;
-                } else {
-                    this[key] = null;
-                }
-            }
-        }
+// Editor uses this - migrate to ScriptAttributes.reservedNames and delete this
+const reservedAttributes = {};
+ScriptAttributes.reservedNames.forEach((value, value2, set) => {
+    reservedAttributes[value] = 1;
+});
+createScript.reservedAttributes = reservedAttributes;
 
-        this.__attributesRaw = null;
+/* eslint-disable jsdoc/check-examples */
+/**
+ * Register a existing class type as a Script Type to {@link ScriptRegistry}. Useful when defining
+ * a ES6 script class that extends {@link ScriptType} (see example).
+ *
+ * @param {typeof ScriptType} script - The existing class type (constructor function) to be
+ * registered as a Script Type. Class must extend {@link ScriptType} (see example). Please note: A
+ * class created using {@link createScript} is auto-registered, and should therefore not be pass
+ * into {@link registerScript} (which would result in swapping out all related script instances).
+ * @param {string} [name] - Optional unique name of the Script Type. By default it will use the
+ * same name as the existing class. If a Script Type with the same name has already been registered
+ * and the new one has a `swap` method defined in its prototype, then it will perform hot swapping
+ * of existing Script Instances on entities using this new Script Type. Note: There is a reserved
+ * list of names that cannot be used, such as list below as well as some starting from `_`
+ * (underscore): system, entity, create, destroy, swap, move, scripts, onEnable, onDisable,
+ * onPostStateChange, has, on, off, fire, once, hasEvent.
+ * @param {AppBase} [app] - Optional application handler, to choose which {@link ScriptRegistry}
+ * to register the script type to. By default it will use `Application.getApplication()` to get
+ * current {@link AppBase}.
+ * @example
+ * // define a ES6 script class
+ * class PlayerController extends pc.ScriptType {
+ *
+ *     initialize() {
+ *         // called once on initialize
+ *     }
+ *
+ *     update(dt) {
+ *         // called each tick
+ *     }
+ * }
+ *
+ * // register the class as a script
+ * pc.registerScript(PlayerController);
+ *
+ * // declare script attributes (Must be after pc.registerScript())
+ * PlayerController.attributes.add('attribute1', {type: 'number'});
+ * @category Script
+ */
+function registerScript(script, name, app) {
+    if (script.legacy) {
+        Debug.error('This project is using the legacy script system. You cannot call pc.registerScript().');
+        return;
     }
 
-    /**
-     * Shorthand function to extend Script Type prototype with list of methods.
-     *
-     * @param {object} methods - Object with methods, where key - is name of method, and value - is function.
-     * @example
-     * var PlayerController = pc.createScript('playerController');
-     *
-     * PlayerController.extend({
-     *     initialize: function () {
-     *         // called once on initialize
-     *     },
-     *     update: function (dt) {
-     *         // called each tick
-     *     }
-     * });
-     */
-    static extend(methods) {
-        for (const key in methods) {
-            if (!methods.hasOwnProperty(key))
-                continue;
+    if (typeof script !== 'function')
+        throw new Error(`script class: '${script}' must be a constructor function (i.e. class).`);
 
-            this.prototype[key] = methods[key];
-        }
-    }
+    if (!(script.prototype instanceof ScriptType))
+        throw new Error(`script class: '${ScriptType.__getScriptName(script)}' does not extend pc.ScriptType.`);
 
-    /**
-     * @function
-     * @name Script#[initialize]
-     * @description Called when script is about to run for the first time.
-     */
+    name = name || script.__name || ScriptType.__getScriptName(script);
 
-    /**
-     * @function
-     * @name Script#[postInitialize]
-     * @description Called after all initialize methods are executed in the same tick or enabling chain of actions.
-     */
+    if (reservedScriptNames.has(name))
+        throw new Error(`script name: '${name}' is reserved, please change script name`);
 
-    /**
-     * @function
-     * @name Script#[update]
-     * @description Called for enabled (running state) scripts on each tick.
-     * @param {number} dt - The delta time in seconds since the last frame.
-     */
+    script.__name = name;
 
-    /**
-     * @function
-     * @name Script#[postUpdate]
-     * @description Called for enabled (running state) scripts on each tick, after update.
-     * @param {number} dt - The delta time in seconds since the last frame.
-     */
+    // add to scripts registry
+    const registry = app ? app.scripts : AppBase.getApplication().scripts;
+    registry.add(script);
 
-    /**
-     * @function
-     * @name Script#[swap]
-     * @description Called when a Script that already exists in the registry
-     * gets redefined. If the new Script has a `swap` method in its prototype,
-     * then it will be executed to perform hot-reload at runtime.
-     * @param {Script} old - Old instance of the script to copy data to the new instance.
-     */
+    ScriptTypes.push(script, script.legacy);
 }
+/* eslint-enable jsdoc/check-examples */
 
-export { Script };
+export { createScript, registerScript, getReservedScriptNames };
diff --git a/src/index.js b/src/index.js
index 10c89ac5a..ee4274508 100644
--- a/src/index.js
+++ b/src/index.js
@@ -360,10 +360,10 @@ export { ElementInput, ElementInputEvent, ElementMouseEvent, ElementSelectEvent,
 export { JsonStandardMaterialParser } from './framework/parsers/material/json-standard-material.js';
 
 // FRAMEWORK /SCRIPTS
-export { createScript, registerScript, getReservedScriptNames } from './framework/script/script-create.js';
+export { createScript, registerScript, getReservedScriptNames } from './framework/script/script.js';
 export { ScriptAttributes } from './framework/script/script-attributes.js';
 export { ScriptRegistry } from './framework/script/script-registry.js';
-export { Script } from './framework/script/script.js';
+export { ScriptType } from './framework/script/script-type.js';
 
 // FRAMEWORK / LOCALIZATION
 export { I18n } from './framework/i18n/i18n.js';
diff --git a/src/platform/graphics/graphics-device.js b/src/platform/graphics/graphics-device.js
index bdfd928c1..ee57634c2 100644
--- a/src/platform/graphics/graphics-device.js
+++ b/src/platform/graphics/graphics-device.js
@@ -264,6 +264,15 @@ class GraphicsDevice extends EventHandler {
       */
     textureHalfFloatRenderable;
 
+    /**
+     * True if small-float textures with format {@link PIXELFORMAT_111110F} can be used as a frame
+     * buffer. This is always true on WebGL2, but optional on WebGPU device.
+     *
+     * @type {boolean}
+     * @readonly
+     */
+    textureRG11B10Renderable = false;
+
      /**
       * True if filtering can be applied when sampling float textures.
       *
@@ -855,6 +864,12 @@ class GraphicsDevice extends EventHandler {
     /**
      * Get a renderable HDR pixel format supported by the graphics device.
      *
+     * Note:
+     * - When the `filterable` parameter is set to false, this function returns one of the supported
+     * formats on the majority of devices apart from some very old iOS and Android devices (99%).
+     * - When the `filterable` parameter is set to true, the function returns a format on a
+     * considerably lower number of devices (70%).
+     *
      * @param {number[]} [formats] - An array of pixel formats to check for support. Can contain:
      *
      * - {@link PIXELFORMAT_111110F}
diff --git a/src/platform/graphics/texture.js b/src/platform/graphics/texture.js
index 96bca13b7..877c57b4a 100644
--- a/src/platform/graphics/texture.js
+++ b/src/platform/graphics/texture.js
@@ -25,6 +25,27 @@ let id = 0;
  * A texture is a container for texel data that can be utilized in a fragment shader. Typically,
  * the texel data represents an image that is mapped over geometry.
  *
+ * Note on **HDR texture format** support:
+ * 1. **As textures**:
+ *     - float (i.e. {@link PIXELFORMAT_RGBA32F}), half-float (i.e. {@link PIXELFORMAT_RGBA16F}) and
+ * small-float ({@link PIXELFORMAT_111110F}) formats are always supported on both WebGL2 and WebGPU
+ * with point sampling.
+ *     - half-float and small-float formats are always supported on WebGL2 and WebGPU with linear
+ * sampling.
+ *     - float formats are supported on WebGL2 and WebGPU with linear sampling only if
+ * {@link GraphicsDevice#textureFloatFilterable} is true.
+ *
+ * 2. **As renderable textures** that can be used as color buffers in a {@link RenderTarget}:
+ *     - on WebGPU, rendering to float and half-float formats is always supported.
+ *     - on WebGPU, rendering to small-float format is supported only if
+ * {@link GraphicsDevice#textureRG11B10Renderable} is true.
+ *     - on WebGL2, rendering to these 3 formats formats is supported only if
+ * {@link GraphicsDevice#textureFloatRenderable} is true.
+ *     - on WebGL2, if {@link GraphicsDevice#textureFloatRenderable} is false, but
+ * {@link GraphicsDevice#textureHalfFloatRenderable} is true, rendering to half-float formats only
+ * is supported. This is the case of many mobile iOS devices.
+ *     - you can determine available renderable HDR format using
+ * {@link GraphicsDevice#getRenderableHdrFormat}.
  * @category Graphics
  */
 class Texture {
@@ -140,8 +161,6 @@ class Texture {
      * - {@link TEXTURETYPE_SWIZZLEGGGR}
      *
      * Defaults to {@link TEXTURETYPE_DEFAULT}.
-     * @param {boolean} [options.fixCubemapSeams] - Specifies whether this cubemap texture requires
-     * special seam fixing shader code to look right. Defaults to false.
      * @param {boolean} [options.flipY] - Specifies whether the texture should be flipped in the
      * Y-direction. Only affects textures with a source that is an image, canvas or video element.
      * Does not affect cubemaps, compressed textures or textures set from raw pixel data. Defaults
@@ -215,7 +234,6 @@ class Texture {
 
         this._storage = options.storage ?? false;
         this._cubemap = options.cubemap ?? false;
-        this.fixCubemapSeams = options.fixCubemapSeams ?? false;
         this._flipY = options.flipY ?? false;
         this._premultiplyAlpha = options.premultiplyAlpha ?? false;
 
diff --git a/src/scene/graphics/env-lighting.js b/src/scene/graphics/env-lighting.js
index 4df3f4839..dd6d99566 100644
--- a/src/scene/graphics/env-lighting.js
+++ b/src/scene/graphics/env-lighting.js
@@ -9,8 +9,6 @@ import {
 } from '../../platform/graphics/constants.js';
 import { DebugGraphics } from '../../platform/graphics/debug-graphics.js';
 
-const fixCubemapSeams = true;
-
 // calculate the number of mipmap levels given texture dimensions
 const calcLevels = (width, height = 0) => {
     return 1 + Math.floor(Math.log2(Math.max(width, height)));
@@ -46,7 +44,6 @@ const createCubemap = (device, size, format, mipmaps) => {
         type: format === PIXELFORMAT_RGBA8 ? RGBA8_TYPE : TEXTURETYPE_DEFAULT,
         addressU: ADDRESS_CLAMP_TO_EDGE,
         addressV: ADDRESS_CLAMP_TO_EDGE,
-        fixCubemapSeams: fixCubemapSeams,
         mipmaps: !!mipmaps
     });
 };
@@ -108,7 +105,6 @@ class EnvLighting {
             type: format === PIXELFORMAT_RGBA8 ? RGBA8_TYPE : TEXTURETYPE_DEFAULT,
             addressU: ADDRESS_CLAMP_TO_EDGE,
             addressV: ADDRESS_CLAMP_TO_EDGE,
-            fixCubemapSeams: false,
             mipmaps: true
         });
 
diff --git a/src/scene/graphics/reproject-texture.js b/src/scene/graphics/reproject-texture.js
index 1c8fc4a1a..4680b6d99 100644
--- a/src/scene/graphics/reproject-texture.js
+++ b/src/scene/graphics/reproject-texture.js
@@ -483,7 +483,6 @@ function reprojectTexture(source, target, options = {}) {
     constantSource.setValue(source);
 
     const constantParams = device.scope.resolve("params");
-    const constantParams2 = device.scope.resolve("params2");
 
     const uvModParam = device.scope.resolve("uvMod");
     if (seamPixels > 0) {
@@ -500,11 +499,6 @@ function reprojectTexture(source, target, options = {}) {
     const params = [
         0,
         specularPower,
-        source.fixCubemapSeams ? 1.0 / source.width : 0.0,          // source seam scale
-        target.fixCubemapSeams ? 1.0 / target.width : 0.0           // target seam scale
-    ];
-
-    const params2 = [
         target.width * target.height * (target.cubemap ? 6 : 1),
         source.width * source.height * (source.cubemap ? 6 : 1)
     ];
@@ -530,7 +524,6 @@ function reprojectTexture(source, target, options = {}) {
             });
             params[0] = f;
             constantParams.setValue(params);
-            constantParams2.setValue(params2);
 
             drawQuadWithShader(device, renderTarget, shader, options?.rect);
 
diff --git a/src/scene/materials/lit-material-options-builder.js b/src/scene/materials/lit-material-options-builder.js
index 78b8df0e7..c8ff4ea4f 100644
--- a/src/scene/materials/lit-material-options-builder.js
+++ b/src/scene/materials/lit-material-options-builder.js
@@ -99,7 +99,6 @@ class LitMaterialOptionsBuilder {
         litOptions.fog = material.useFog ? scene.fog : 'none';
         litOptions.gamma = material.useGammaTonemap ? scene.gammaCorrection : GAMMA_NONE;
         litOptions.toneMap = material.useGammaTonemap ? scene.toneMapping : -1;
-        litOptions.fixSeams = false;
 
         // source of reflections
         if (material.useSkybox && scene.envAtlas && scene.skybox) {
diff --git a/src/scene/materials/standard-material-options-builder.js b/src/scene/materials/standard-material-options-builder.js
index b4e35897a..03ce42a87 100644
--- a/src/scene/materials/standard-material-options-builder.js
+++ b/src/scene/materials/standard-material-options-builder.js
@@ -301,7 +301,6 @@ class StandardMaterialOptionsBuilder {
         options.litOptions.fog = stdMat.useFog ? scene.fog : 'none';
         options.litOptions.gamma = stdMat.useGammaTonemap ? scene.gammaCorrection : GAMMA_NONE;
         options.litOptions.toneMap = stdMat.useGammaTonemap ? scene.toneMapping : -1;
-        options.litOptions.fixSeams = (stdMat.cubeMap ? stdMat.cubeMap.fixCubemapSeams : false);
 
         const isPhong = stdMat.shadingModel === SPECULAR_PHONG;
 
diff --git a/src/scene/materials/standard-material.js b/src/scene/materials/standard-material.js
index 287343992..027a2bb1a 100644
--- a/src/scene/materials/standard-material.js
+++ b/src/scene/materials/standard-material.js
@@ -993,9 +993,7 @@ function _defineTex2D(name, channel = "rgb", vertexColor = true, uv = 0) {
         defaultValue: null,
         dirtyShaderFunc: (oldValue, newValue) => {
             return !!oldValue !== !!newValue ||
-                oldValue && (oldValue.type !== newValue.type ||
-                             oldValue.fixCubemapSeams !== newValue.fixCubemapSeams ||
-                             oldValue.format !== newValue.format);
+                oldValue && (oldValue.type !== newValue.type || oldValue.format !== newValue.format);
         }
     });
 
diff --git a/src/scene/shader-lib/chunks/chunks.js b/src/scene/shader-lib/chunks/chunks.js
index 9057c4288..7270c0677 100644
--- a/src/scene/shader-lib/chunks/chunks.js
+++ b/src/scene/shader-lib/chunks/chunks.js
@@ -45,8 +45,6 @@ import extensionPS from './lit/frag/extension.js';
 import extensionVS from './lit/vert/extension.js';
 import falloffInvSquaredPS from './lit/frag/falloffInvSquared.js';
 import falloffLinearPS from './lit/frag/falloffLinear.js';
-import fixCubemapSeamsNonePS from './common/frag/fixCubemapSeamsNone.js';
-import fixCubemapSeamsStretchPS from './common/frag/fixCubemapSeamsStretch.js';
 import floatUnpackingPS from './lit/frag/float-unpacking.js';
 import fogExpPS from './lit/frag/fogExp.js';
 import fogExp2PS from './lit/frag/fogExp2.js';
@@ -255,8 +253,6 @@ const shaderChunks = {
     extensionVS,
     falloffInvSquaredPS,
     falloffLinearPS,
-    fixCubemapSeamsNonePS,
-    fixCubemapSeamsStretchPS,
     floatUnpackingPS,
     fogExpPS,
     fogExp2PS,
diff --git a/src/scene/shader-lib/chunks/common/frag/fixCubemapSeamsNone.js b/src/scene/shader-lib/chunks/common/frag/fixCubemapSeamsNone.js
deleted file mode 100644
index e3e735f48..000000000
--- a/src/scene/shader-lib/chunks/common/frag/fixCubemapSeamsNone.js
+++ /dev/null
@@ -1,21 +0,0 @@
-export default /* glsl */`
-vec3 fixSeams(vec3 vec, float mipmapIndex) {
-    return vec;
-}
-
-vec3 fixSeams(vec3 vec) {
-    return vec;
-}
-
-vec3 fixSeamsStatic(vec3 vec, float invRecMipSize) {
-    return vec;
-}
-
-vec3 calcSeam(vec3 vec) {
-    return vec3(0);
-}
-
-vec3 applySeam(vec3 vec, vec3 seam, float scale) {
-    return vec;
-}
-`;
diff --git a/src/scene/shader-lib/chunks/common/frag/fixCubemapSeamsStretch.js b/src/scene/shader-lib/chunks/common/frag/fixCubemapSeamsStretch.js
deleted file mode 100644
index c7ff02d01..000000000
--- a/src/scene/shader-lib/chunks/common/frag/fixCubemapSeamsStretch.js
+++ /dev/null
@@ -1,43 +0,0 @@
-export default /* glsl */`
-vec3 fixSeams(vec3 vec, float mipmapIndex) {
-    vec3 avec = abs(vec);
-    float scale = 1.0 - exp2(mipmapIndex) / 128.0;
-    float M = max(max(avec.x, avec.y), avec.z);
-    if (avec.x != M) vec.x *= scale;
-    if (avec.y != M) vec.y *= scale;
-    if (avec.z != M) vec.z *= scale;
-    return vec;
-}
-
-vec3 fixSeams(vec3 vec) {
-    vec3 avec = abs(vec);
-    float scale = 1.0 - 1.0 / 128.0;
-    float M = max(max(avec.x, avec.y), avec.z);
-    if (avec.x != M) vec.x *= scale;
-    if (avec.y != M) vec.y *= scale;
-    if (avec.z != M) vec.z *= scale;
-    return vec;
-}
-
-vec3 fixSeamsStatic(vec3 vec, float invRecMipSize) {
-    vec3 avec = abs(vec);
-    float scale = invRecMipSize;
-    float M = max(max(avec.x, avec.y), avec.z);
-    if (avec.x != M) vec.x *= scale;
-    if (avec.y != M) vec.y *= scale;
-    if (avec.z != M) vec.z *= scale;
-    return vec;
-}
-
-vec3 calcSeam(vec3 vec) {
-    vec3 avec = abs(vec);
-    float M = max(avec.x, max(avec.y, avec.z));
-    return vec3(avec.x != M ? 1.0 : 0.0,
-                avec.y != M ? 1.0 : 0.0,
-                avec.z != M ? 1.0 : 0.0);
-}
-
-vec3 applySeam(vec3 vec, vec3 seam, float scale) {
-    return vec * (seam * -scale + vec3(1.0));
-}
-`;
diff --git a/src/scene/shader-lib/chunks/common/frag/reproject.js b/src/scene/shader-lib/chunks/common/frag/reproject.js
index a0963738c..d89548ac9 100644
--- a/src/scene/shader-lib/chunks/common/frag/reproject.js
+++ b/src/scene/shader-lib/chunks/common/frag/reproject.js
@@ -32,22 +32,14 @@ varying vec2 vUv0;
 // params:
 // x - target cubemap face 0..6
 // y - specular power (when prefiltering)
-// z - source cubemap seam scale (0 to disable)
-// w - target cubemap size for seam calc (0 to disable)
+// z - target image total pixels
+// w - source cubemap size
 uniform vec4 params;
 
-// params2:
-// x - target image total pixels
-// y - source cubemap size
-uniform vec2 params2;
-
 float targetFace() { return params.x; }
 float specularPower() { return params.y; }
-float sourceCubeSeamScale() { return params.z; }
-float targetCubeSeamScale() { return params.w; }
-
-float targetTotalPixels() { return params2.x; }
-float sourceTotalPixels() { return params2.y; }
+float targetTotalPixels() { return params.z; }
+float sourceTotalPixels() { return params.w; }
 
 float PI = 3.141592653589793;
 
@@ -122,7 +114,7 @@ vec2 octEncode(in vec3 v) {
 
 #ifdef CUBEMAP_SOURCE
     vec4 sampleCubemap(vec3 dir) {
-        return textureCube(sourceCube, modifySeams(dir, 1.0 - sourceCubeSeamScale()));
+        return textureCube(sourceCube, modifySeams(dir, 1.0));
     }
 
     vec4 sampleCubemap(vec2 sph) {
@@ -130,7 +122,7 @@ vec2 octEncode(in vec3 v) {
 }
 
     vec4 sampleCubemap(vec3 dir, float mipLevel) {
-        return textureCubeLodEXT(sourceCube, modifySeams(dir, 1.0 - exp2(mipLevel) * sourceCubeSeamScale()), mipLevel);
+        return textureCubeLodEXT(sourceCube, modifySeams(dir, 1.0), mipLevel);
     }
 
     vec4 sampleCubemap(vec2 sph, float mipLevel) {
@@ -195,7 +187,7 @@ vec3 getDirectionCubemap() {
         vec = vec3(-st.x, -st.y, -1);
     }
 
-    return normalize(modifySeams(vec, 1.0 / (1.0 - targetCubeSeamScale())));
+    return normalize(modifySeams(vec, 1.0));
 }
 
 mat3 matrixFromVector(vec3 n) { // frisvad
diff --git a/src/scene/shader-lib/chunks/lit/frag/reflectionCube.js b/src/scene/shader-lib/chunks/lit/frag/reflectionCube.js
index a188064ee..37e7f98ab 100644
--- a/src/scene/shader-lib/chunks/lit/frag/reflectionCube.js
+++ b/src/scene/shader-lib/chunks/lit/frag/reflectionCube.js
@@ -3,7 +3,7 @@ uniform samplerCube texture_cubeMap;
 uniform float material_reflectivity;
 
 vec3 calcReflection(vec3 reflDir, float gloss) {
-    vec3 lookupVec = fixSeams(cubeMapProject(reflDir));
+    vec3 lookupVec = cubeMapProject(reflDir);
     lookupVec.x *= -1.0;
     return $DECODE(textureCube(texture_cubeMap, lookupVec));
 }
diff --git a/src/scene/shader-lib/chunks/lit/frag/reflectionEnvHQ.js b/src/scene/shader-lib/chunks/lit/frag/reflectionEnvHQ.js
index 1782a2660..10e513c28 100644
--- a/src/scene/shader-lib/chunks/lit/frag/reflectionEnvHQ.js
+++ b/src/scene/shader-lib/chunks/lit/frag/reflectionEnvHQ.js
@@ -15,7 +15,7 @@ vec3 calcReflection(vec3 reflDir, float gloss) {
     float ilevel = floor(level);
     float flevel = level - ilevel;
 
-    vec3 sharp = $DECODE_CUBEMAP(textureCube(texture_cubeMap, fixSeams(dir)));
+    vec3 sharp = $DECODE_CUBEMAP(textureCube(texture_cubeMap, dir));
     vec3 roughA = $DECODE(texture2D(texture_envAtlas, mapRoughnessUv(uv, ilevel)));
     vec3 roughB = $DECODE(texture2D(texture_envAtlas, mapRoughnessUv(uv, ilevel + 1.0)));
 
diff --git a/src/scene/shader-lib/chunks/skybox/frag/skyboxHDR.js b/src/scene/shader-lib/chunks/skybox/frag/skyboxHDR.js
index a08c59c46..288ed91a2 100644
--- a/src/scene/shader-lib/chunks/skybox/frag/skyboxHDR.js
+++ b/src/scene/shader-lib/chunks/skybox/frag/skyboxHDR.js
@@ -26,7 +26,7 @@ void main(void) {
     #endif
 
     dir.x *= -1.0;
-    vec3 linear = SKYBOX_DECODE_FNC(textureCube(texture_cubeMap, fixSeamsStatic(dir, SKYBOX_MIP)));
+    vec3 linear = SKYBOX_DECODE_FNC(textureCube(texture_cubeMap, dir));
     gl_FragColor = vec4(gammaCorrectOutput(toneMap(processEnvironment(linear))), 1.0);
 }
 `;
diff --git a/src/scene/shader-lib/programs/lit-shader-options.js b/src/scene/shader-lib/programs/lit-shader-options.js
index 85b20ca67..c44e4014c 100644
--- a/src/scene/shader-lib/programs/lit-shader-options.js
+++ b/src/scene/shader-lib/programs/lit-shader-options.js
@@ -266,14 +266,6 @@ class LitShaderOptions {
      */
     toneMap = -1;
 
-    /**
-     * If cubemaps require seam fixing (see the `fixCubemapSeams` property of the options object
-     * passed to the {@link Texture} constructor).
-     *
-     * @type {boolean}
-     */
-    fixSeams = false;
-
     /**
      * One of "envAtlasHQ", "envAtlas", "cubeMap", "sphereMap".
      *
diff --git a/src/scene/shader-lib/programs/lit-shader.js b/src/scene/shader-lib/programs/lit-shader.js
index 028dc6a85..24d02b8e7 100644
--- a/src/scene/shader-lib/programs/lit-shader.js
+++ b/src/scene/shader-lib/programs/lit-shader.js
@@ -780,7 +780,6 @@ class LitShader {
         }
 
         if (options.reflectionSource === 'envAtlasHQ') {
-            func.append(options.fixSeams ? chunks.fixCubemapSeamsStretchPS : chunks.fixCubemapSeamsNonePS);
             func.append(chunks.envAtlasPS);
             func.append(chunks.reflectionEnvHQPS
                 .replace(/\$DECODE_CUBEMAP/g, ChunkUtils.decodeFunc(options.reflectionCubemapEncoding))
@@ -790,7 +789,6 @@ class LitShader {
             func.append(chunks.envAtlasPS);
             func.append(chunks.reflectionEnvPS.replace(/\$DECODE/g, ChunkUtils.decodeFunc(options.reflectionEncoding)));
         } else if (options.reflectionSource === 'cubeMap') {
-            func.append(options.fixSeams ? chunks.fixCubemapSeamsStretchPS : chunks.fixCubemapSeamsNonePS);
             func.append(chunks.reflectionCubePS.replace(/\$DECODE/g, ChunkUtils.decodeFunc(options.reflectionEncoding)));
         } else if (options.reflectionSource === 'sphereMap') {
             func.append(chunks.reflectionSpherePS.replace(/\$DECODE/g, ChunkUtils.decodeFunc(options.reflectionEncoding)));
diff --git a/src/scene/shader-lib/programs/skybox.js b/src/scene/shader-lib/programs/skybox.js
index fbe7141b0..6b32c7f40 100644
--- a/src/scene/shader-lib/programs/skybox.js
+++ b/src/scene/shader-lib/programs/skybox.js
@@ -6,8 +6,6 @@ import { ShaderUtils } from '../../../platform/graphics/shader-utils.js';
 import { ShaderGenerator } from './shader-generator.js';
 import { SKYTYPE_INFINITE } from '../../constants.js';
 
-const mip2size = [128, 64, /* 32 */ 16, 8, 4, 2];
-
 const fShader = `
     #include "decodePS"
     #include "gamma"
@@ -15,7 +13,6 @@ const fShader = `
     #include "envMultiplyPS"
 
     #ifdef SKY_CUBEMAP
-        #include "cubemapSeams"
         #include "skyboxHDRPS"
     #else
         #include "sphericalPS"
@@ -38,7 +35,6 @@ class ShaderGeneratorSkybox extends ShaderGenerator {
         if (options.skymesh !== SKYTYPE_INFINITE) defines.set('SKYMESH', '');
         if (options.type === 'cubemap') {
             defines.set('SKY_CUBEMAP', '');
-            defines.set('SKYBOX_MIP', (1 - 1 / mip2size[options.mip]).toString());
         }
 
         // includes
@@ -49,7 +45,6 @@ class ShaderGeneratorSkybox extends ShaderGenerator {
         includes.set('envMultiplyPS', shaderChunks.envMultiplyPS);
 
         if (options.type === 'cubemap') {
-            includes.set('cubemapSeams', options.mip ? shaderChunks.fixCubemapSeamsStretchPS : shaderChunks.fixCubemapSeamsNonePS);
             includes.set('skyboxHDRPS', shaderChunks.skyboxHDRPS);
         } else {
             includes.set('sphericalPS', shaderChunks.sphericalPS);
diff --git a/src/scene/skybox/sky-mesh.js b/src/scene/skybox/sky-mesh.js
index 3b1152671..1a5149fd3 100644
--- a/src/scene/skybox/sky-mesh.js
+++ b/src/scene/skybox/sky-mesh.js
@@ -45,8 +45,7 @@ class SkyMesh {
 
             if (texture.cubemap) {
                 options.type = 'cubemap';
-                options.mip = texture.fixCubemapSeams ? scene.skyboxMip : 0;
-                options.fixSeams = texture.fixCubemapSeams;
+                options.mip = scene.skyboxMip;
             } else {
                 options.type = 'envAtlas';
             }
diff --git a/utils/plugins/rollup-types-fixup.mjs b/utils/plugins/rollup-types-fixup.mjs
index 37d9d181a..8bc60ebd7 100644
--- a/utils/plugins/rollup-types-fixup.mjs
+++ b/utils/plugins/rollup-types-fixup.mjs
@@ -209,7 +209,7 @@ import { Texture } from '../../platform/graphics/texture.js';
 `
     }
 }, {
-    path: `${TYPES_PATH}/framework/script/script.d.ts`,
+    path: `${TYPES_PATH}/framework/script/script-type.d.ts`,
     replacement: {
         from: 'get enabled(): boolean;',
         to: `get enabled(): boolean;
@@ -232,12 +232,12 @@ import { Texture } from '../../platform/graphics/texture.js';
      */
     postUpdate?(dt: number): void;
     /**
-     * Called when a Script that already exists in the registry gets redefined. If the new
-     * Script has a \`swap\` method in its prototype, then it will be executed to perform
+     * Called when a ScriptType that already exists in the registry gets redefined. If the new
+     * ScriptType has a \`swap\` method in its prototype, then it will be executed to perform
      * hot-reload at runtime.
-     * @param old - Old instance of the script to copy data to the new instance.
+     * @param old - Old instance of the scriptType to copy data to the new instance.
      */
-    swap?(old: Script): void;
+    swap?(old: ScriptType): void;
 `
     }
 }];


Test patch:
diff --git a/test/framework/entity.test.mjs b/test/framework/entity.test.mjs
index a761962c7..4cd5a2a55 100644
--- a/test/framework/entity.test.mjs
+++ b/test/framework/entity.test.mjs
@@ -1,4 +1,4 @@
-import { createScript } from '../../src/framework/script/script-create.js';
+import { createScript } from '../../src/framework/script/script.js';
 import { Color } from '../../src/core/math/color.js';
 
 import { AnimComponent } from '../../src/framework/components/anim/component.js';