Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create a TModel type alias #783

Open
zepumph opened this issue Feb 28, 2022 · 21 comments
Open

Create a TModel type alias #783

zepumph opened this issue Feb 28, 2022 · 21 comments

Comments

@zepumph
Copy link
Member

zepumph commented Feb 28, 2022

This will have a step function. For improving upon type safety and documentation. It also allows for a space to expand the API in the future.

phetsims/ratio-and-proportion#405

@zepumph zepumph self-assigned this Feb 28, 2022
@zepumph
Copy link
Member Author

zepumph commented Feb 28, 2022

Maybe this get's a reset function too?

@zepumph zepumph changed the title Create and IModel interface Create an IModel interface Feb 28, 2022
@samreid
Copy link
Member

samreid commented Jul 15, 2022

I committed IModel today for a different issue. But it doesn't have a reset method yet.

@samreid samreid assigned samreid and unassigned zepumph Jul 15, 2022
@samreid
Copy link
Member

samreid commented Jul 18, 2022

I don't see any calls to reset in joist, so I'm not sure the advantage of adding that as an optional part of the interface. @zepumph can this be closed?

UPDATE: Tagging for phetsims/tasks#1111

@zepumph
Copy link
Member Author

zepumph commented Jul 19, 2022

In 0d6f15d I made IModel the default for Screen, which was nice to not have to give it parameters when really it is quite general.

@pixelzoom
Copy link
Contributor

Based on conclusions in phetsims/chipper#1283, and @samreid's proposal to ban interface... Perhaps type should be used instead of interface.

@zepumph
Copy link
Member Author

zepumph commented Jul 19, 2022

It is:

type IModel = {

zepumph added a commit to phetsims/bamboo that referenced this issue Jul 19, 2022
…btypes as parameters to createView, update typescript to 4.7, phetsims/joist#783
zepumph added a commit to phetsims/quadrilateral that referenced this issue Jul 19, 2022
…btypes as parameters to createView, update typescript to 4.7, phetsims/joist#783
zepumph added a commit to phetsims/mobius that referenced this issue Jul 19, 2022
…btypes as parameters to createView, update typescript to 4.7, phetsims/joist#783
zepumph added a commit to phetsims/sun that referenced this issue Jul 19, 2022
…btypes as parameters to createView, update typescript to 4.7, phetsims/joist#783
zepumph added a commit to phetsims/wilder that referenced this issue Jul 19, 2022
…btypes as parameters to createView, update typescript to 4.7, phetsims/joist#783
zepumph added a commit to phetsims/models-of-the-hydrogen-atom that referenced this issue Jul 19, 2022
…btypes as parameters to createView, update typescript to 4.7, phetsims/joist#783
zepumph added a commit to phetsims/bending-light that referenced this issue Jul 19, 2022
…btypes as parameters to createView, update typescript to 4.7, phetsims/joist#783
zepumph added a commit to phetsims/gravity-and-orbits that referenced this issue Jul 19, 2022
…btypes as parameters to createView, update typescript to 4.7, phetsims/joist#783
zepumph added a commit to phetsims/circuit-construction-kit-common that referenced this issue Jul 19, 2022
…btypes as parameters to createView, update typescript to 4.7, phetsims/joist#783
zepumph added a commit to phetsims/nitroglycerin that referenced this issue Jul 19, 2022
…btypes as parameters to createView, update typescript to 4.7, phetsims/joist#783
zepumph added a commit to phetsims/scenery-phet that referenced this issue Jul 19, 2022
…btypes as parameters to createView, update typescript to 4.7, phetsims/joist#783
zepumph added a commit to phetsims/number-play that referenced this issue Jul 19, 2022
…btypes as parameters to createView, update typescript to 4.7, phetsims/joist#783
zepumph added a commit to phetsims/vegas that referenced this issue Jul 19, 2022
…btypes as parameters to createView, update typescript to 4.7, phetsims/joist#783
zepumph added a commit that referenced this issue Jul 19, 2022
…btypes as parameters to createView, update typescript to 4.7, #783
zepumph added a commit to phetsims/chipper that referenced this issue Jul 19, 2022
…btypes as parameters to createView, update typescript to 4.7, phetsims/joist#783
@pixelzoom
Copy link
Contributor

pixelzoom commented Aug 4, 2022

I can't say that I'm wild about the approach in the patch in #783 (comment). But I don't have a better suggestion.

EDIT: I really dislike this bit. And is it correct?

+export default class Sim<M1 extends IModel, V1 extends ScreenView,
+  M2 extends IModel, V2 extends ScreenView,
+  M3 extends IModel, V3 extends ScreenView,
+  M4 extends IModel, V4 extends ScreenView,
+  M5 extends IModel, V5 extends ScreenView> extends PhetioObject {

+  public constructor( name: string, allSimScreens: [ Screen<M1, V1> ], providedOptions?: SimOptions );
+  public constructor( name: string, allSimScreens: [ Screen<M1, V1> ] | [ Screen<M1, V1>, Screen<M2, V2> ], providedOptions?: SimOptions );
+  public constructor( name: string, allSimScreens: [ Screen<M1, V1> ] | [ Screen<M1, V1>, Screen<M2, V2> ] | [ Screen<M1, V1>, Screen<M2, V2>, Screen<M3, V3> ], providedOptions?: SimOptions );
+  public constructor( name: string, allSimScreens: [ Screen<M1, V1> ] | [ Screen<M1, V1>, Screen<M2, V2> ] | [ Screen<M1, V1>, Screen<M2, V2>, Screen<M3, V3> ] | [ Screen<M1, V1>, Screen<M2, V2>, Screen<M3, V3>, Screen<M4, V4> ], providedOptions?: SimOptions );
+  public constructor( name: string, allSimScreens: [ Screen<M1, V1> ] | [ Screen<M1, V1>, Screen<M2, V2> ] | [ Screen<M1, V1>, Screen<M2, V2>, Screen<M3, V3> ] | [ Screen<M1, V1>, Screen<M2, V2>, Screen<M3, V3>, Screen<M4, V4> ] | [ Screen<M1, V1>, Screen<M2, V2>, Screen<M3, V3>, Screen<M4, V4>, Screen<M5, V5> ], providedOptions?: SimOptions );
+  public constructor( name: string, allSimScreens: [ Screen<M1, V1> ] | [ Screen<M1, V1>, Screen<M2, V2> ] | [ Screen<M1, V1>, Screen<M2, V2>, Screen<M3, V3> ] | [ Screen<M1, V1>, Screen<M2, V2>, Screen<M3, V3>, Screen<M4, V4> ] | [ Screen<M1, V1>, Screen<M2, V2>, Screen<M3, V3>, Screen<M4, V4>, Screen<M5, V5> ], providedOptions?: SimOptions ) {

That said...

If create is the new way of instantiating Sim, then Sim's constructor should be protected.

IModel should be renamed to TModel, or perhaps something without a prefix. (I don't feel strongly about eradicating the 'T' prefix.)

@pixelzoom pixelzoom assigned samreid and unassigned pixelzoom Aug 4, 2022
@samreid
Copy link
Member

samreid commented Aug 8, 2022

If create is the new way of instantiating Sim, then Sim's constructor should be protected.

The proposed strategy gets type inference to work well in all sites that use new Sim. For instance, in Fourier, it could infer all the model and view types from this part:

  const sim = new Sim( fourierMakingWavesTitleString, [
    new DiscreteScreen( { tandem: Tandem.ROOT.createTandem( 'discreteScreen' ) } ),
    new WaveGameScreen( { tandem: Tandem.ROOT.createTandem( 'waveGameScreen' ) } ),
    new WavePacketScreen( { tandem: Tandem.ROOT.createTandem( 'wavePacketScreen' ) } )
  ], simOptions );
  sim.start();

Sim.create was our workaround for sites that extend Sim where inference cannot be used. But maybe it would be better if all sites could use new Sim.

EDIT: I really dislike this bit. And is it correct?

I skimmed it and did not see any obvious defects.

Let's discuss further before proceeding.

@pixelzoom
Copy link
Contributor

@samreid let me know when you'd like to discuss.

@samreid
Copy link
Member

samreid commented Aug 27, 2022

I added a note to my calendar to reach out to @pixelzoom in the coming week. Self-unassigning until then.

UPDATE: Perhaps an on-hold label would be good instead.

@samreid
Copy link
Member

samreid commented Aug 29, 2022

Removing the hold to update the patch before discussing with @pixelzoom

@samreid
Copy link
Member

samreid commented Aug 29, 2022

Current patch:

Index: main/joist/js/toolbar/VoicingToolbarAlertManager.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/main/joist/js/toolbar/VoicingToolbarAlertManager.ts b/main/joist/js/toolbar/VoicingToolbarAlertManager.ts
--- a/main/joist/js/toolbar/VoicingToolbarAlertManager.ts	(revision 967a7adee65fefc039bffad000ae7726fc668b95)
+++ b/main/joist/js/toolbar/VoicingToolbarAlertManager.ts	(date 1661801709410)
@@ -10,17 +10,17 @@
 
 import TReadOnlyProperty from '../../../axon/js/TReadOnlyProperty.js';
 import joist from '../joist.js';
-import Screen from '../Screen.js';
+import { UnknownScreen } from '../Screen.js';
 
 class VoicingToolbarAlertManager {
 
   // The active Screen for the simulation, to generate Voicing descriptions that are related to the active screen.
-  private readonly screenProperty: TReadOnlyProperty<Screen>;
+  private readonly screenProperty: TReadOnlyProperty<UnknownScreen>;
 
   /**
    * @param screenProperty - indicates the active screen
    */
-  public constructor( screenProperty: TReadOnlyProperty<Screen> ) {
+  public constructor( screenProperty: TReadOnlyProperty<UnknownScreen> ) {
     this.screenProperty = screenProperty;
   }
 
Index: main/joist/js/HomeScreenView.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/main/joist/js/HomeScreenView.ts b/main/joist/js/HomeScreenView.ts
--- a/main/joist/js/HomeScreenView.ts	(revision 967a7adee65fefc039bffad000ae7726fc668b95)
+++ b/main/joist/js/HomeScreenView.ts	(date 1661803022498)
@@ -16,18 +16,18 @@
 import joist from './joist.js';
 import joistStrings from './joistStrings.js';
 import ScreenView, { ScreenViewOptions } from './ScreenView.js';
-import Screen from './Screen.js';
+import Screen, { UnknownScreen } from './Screen.js';
 import HomeScreenModel from './HomeScreenModel.js';
 import Property from '../../axon/js/Property.js';
 import optionize from '../../phet-core/js/optionize.js';
 import IntentionalAny from '../../phet-core/js/types/IntentionalAny.js';
 import TReadOnlyProperty from '../../axon/js/TReadOnlyProperty.js';
 import PickRequired from '../../phet-core/js/types/PickRequired.js';
+import HomeScreen from './HomeScreen.js';
+import TModel from './TModel.js';
 
 const homeScreenDescriptionPatternString = joistStrings.a11y.homeScreenDescriptionPattern;
 
-type GeneralScreen = Screen<IntentionalAny>;
-
 type SelfOptions = {
 
   // to display below the icons as a warning if available
@@ -36,10 +36,10 @@
 
 type HomeScreenViewOptions = SelfOptions & PickRequired<ScreenViewOptions, 'tandem'>;
 
-class HomeScreenView extends ScreenView {
+class HomeScreenView<M1 extends TModel, V1 extends ScreenView, M2 extends TModel, V2 extends ScreenView, M3 extends TModel, V3 extends ScreenView, M4 extends TModel, V4 extends ScreenView, M5 extends TModel, V5 extends ScreenView, M6 extends TModel, V6 extends ScreenView> extends ScreenView {
 
   private homeScreenScreenSummaryIntro!: string;
-  private selectedScreenProperty: Property<GeneralScreen>;
+  private selectedScreenProperty: Property<( Screen<M1, V1> | Screen<M2, V2> | Screen<M3, V3> | Screen<M4, V4> | Screen<M5, V5> | Screen<M6, V6> )>;
   public screenButtons: HomeScreenButton[];
 
   // NOTE: In https://github.com/phetsims/joist/issues/640, we attempted to use ScreenView.DEFAULT_LAYOUT_BOUNDS here.
@@ -55,7 +55,7 @@
    * @param model
    * @param [providedOptions]
    */
-  public constructor( simNameProperty: TReadOnlyProperty<string>, model: HomeScreenModel, providedOptions?: HomeScreenViewOptions ) {
+  public constructor( simNameProperty: TReadOnlyProperty<string>, model: HomeScreenModel<M1, V1, M2, V2, M3, V3, M4, V4, M5, V5, M6, V6>, providedOptions?: HomeScreenViewOptions ) {
 
     const options = optionize<HomeScreenViewOptions, SelfOptions, ScreenViewOptions>()( {
       layoutBounds: HomeScreenView.LAYOUT_BOUNDS,
@@ -95,7 +95,7 @@
 
     const buttonGroupTandem = options.tandem.createTandem( 'buttonGroup' );
 
-    this.screenButtons = _.map( model.simScreens, ( screen: GeneralScreen ) => {
+    this.screenButtons = _.map( model.simScreens, ( screen: UnknownScreen ) => {
 
       assert && assert( screen.nameProperty.value, `name is required for screen ${model.simScreens.indexOf( screen )}` );
       assert && assert( screen.homeScreenIcon, `homeScreenIcon is required for screen ${screen.nameProperty.value}` );
Index: main/joist/js/Heartbeat.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/main/joist/js/Heartbeat.ts b/main/joist/js/Heartbeat.ts
--- a/main/joist/js/Heartbeat.ts	(revision 967a7adee65fefc039bffad000ae7726fc668b95)
+++ b/main/joist/js/Heartbeat.ts	(date 1661800779105)
@@ -9,7 +9,7 @@
  */
 
 import joist from './joist.js';
-import Sim from './Sim.js';
+import Sim, { UnknownSim } from './Sim.js';
 
 // variables
 let started = false;
@@ -22,7 +22,7 @@
   /**
    * Initializes the heartbeat div to begin ticking to prevent Safari from going to sleep.
    */
-  start: function( sim: Sim ): void {
+  start: function( sim: UnknownSim ): void {
     assert && assert( !started, 'Heartbeat can only be started once' );
     started = true;
 
Index: main/joist/js/SimInfo.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/main/joist/js/SimInfo.ts b/main/joist/js/SimInfo.ts
--- a/main/joist/js/SimInfo.ts	(revision 967a7adee65fefc039bffad000ae7726fc668b95)
+++ b/main/joist/js/SimInfo.ts	(date 1661801932375)
@@ -20,9 +20,9 @@
 import ObjectLiteralIO from '../../tandem/js/types/ObjectLiteralIO.js';
 import StringIO from '../../tandem/js/types/StringIO.js';
 import joist from './joist.js';
-import Screen from './Screen.js';
+import Screen, { UnknownScreen } from './Screen.js';
 import packageJSON from './packageJSON.js';
-import Sim from './Sim.js';
+import Sim, { UnknownSim } from './Sim.js';
 
 export type ScreenState = {
   name: string;
@@ -54,7 +54,7 @@
 class SimInfo extends PhetioObject {
   public readonly info: SimInfoState = {} as SimInfoState;
 
-  public constructor( sim: Sim ) {
+  public constructor( sim: UnknownSim ) {
     super( {
       tandem: Tandem.GENERAL_MODEL.createTandem( 'simInfo' ),
       phetioType: SimInfo.SimInfoIO,
@@ -110,7 +110,7 @@
     this.putInfo( 'simName', sim.simNameProperty.value );
     this.putInfo( 'simVersion', sim.version );
     this.putInfo( 'repoName', packageJSON.name );
-    this.putInfo( 'screens', sim.screens.map( ( screen: Screen ) => {
+    this.putInfo( 'screens', sim.screens.map( ( screen: UnknownScreen ) => {
       const screenObject: ScreenState = {
 
         // likely null for single screen sims, so use the sim name as a default
Index: main/joist/js/audioManager.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/main/joist/js/audioManager.ts b/main/joist/js/audioManager.ts
--- a/main/joist/js/audioManager.ts	(revision 967a7adee65fefc039bffad000ae7726fc668b95)
+++ b/main/joist/js/audioManager.ts	(date 1661800676843)
@@ -26,7 +26,7 @@
 import PhetioObject from '../../tandem/js/PhetioObject.js';
 import Tandem from '../../tandem/js/Tandem.js';
 import joist from './joist.js';
-import Sim from './Sim.js';
+import Sim, { UnknownSim } from './Sim.js';
 import TReadOnlyProperty from '../../axon/js/TReadOnlyProperty.js';
 
 class AudioManager extends PhetioObject {
@@ -84,7 +84,7 @@
   /**
    * Initialize the AudioManager and subcomponents.
    */
-  public initialize( sim: Sim ): void {
+  public initialize( sim: UnknownSim ): void {
 
     if ( sim.preferencesModel.audioModel.supportsSound ) {
       soundManager.initialize(
Index: main/joist/js/PhetButton.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/main/joist/js/PhetButton.ts b/main/joist/js/PhetButton.ts
--- a/main/joist/js/PhetButton.ts	(revision 967a7adee65fefc039bffad000ae7726fc668b95)
+++ b/main/joist/js/PhetButton.ts	(date 1661800945606)
@@ -20,7 +20,7 @@
 import joistStrings from './joistStrings.js';
 import KebabMenuIcon from './KebabMenuIcon.js';
 import PhetMenu from './PhetMenu.js';
-import Sim from './Sim.js';
+import Sim, { UnknownSim } from './Sim.js';
 import updateCheck from './updateCheck.js';
 import UpdateState from './UpdateState.js';
 
@@ -35,7 +35,7 @@
 class PhetButton extends JoistButton {
   public static PhetButtonIO: IOType;
 
-  public constructor( sim: Sim, backgroundFillProperty: TReadOnlyProperty<Color>, tandem: Tandem ) {
+  public constructor( sim: UnknownSim, backgroundFillProperty: TReadOnlyProperty<Color>, tandem: Tandem ) {
 
     // Dynamic modules are loaded in simLauncher and accessed through their namespace
     const Brand: TBrand = phet.brand.Brand;
Index: main/wave-interference/js/wave-interference-main.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/main/wave-interference/js/wave-interference-main.ts b/main/wave-interference/js/wave-interference-main.ts
--- a/main/wave-interference/js/wave-interference-main.ts	(revision 19d020ea56b6652cfa0aefb9c2b0990fada24fbf)
+++ b/main/wave-interference/js/wave-interference-main.ts	(date 1661802207786)
@@ -42,12 +42,11 @@
     matchVertical: false
   } );
 
-  const screens = [
+  const sim = new Sim( waveInterferenceTitleString, [
     new WavesScreen( alignGroup ),
     new InterferenceScreen( alignGroup ),
     new SlitsScreen( alignGroup ),
     new DiffractionScreen()
-  ];
-  const sim = new Sim( waveInterferenceTitleString, screens, simOptions );
+  ], simOptions );
   sim.start();
 } );
\ No newline at end of file
Index: main/sun/js/Dialog.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/main/sun/js/Dialog.ts b/main/sun/js/Dialog.ts
--- a/main/sun/js/Dialog.ts	(revision a8a5b6aea0640527cdced0a6d5115139421abf2d)
+++ b/main/sun/js/Dialog.ts	(date 1661801849122)
@@ -12,7 +12,7 @@
 import Multilink, { UnknownMultilink } from '../../axon/js/Multilink.js';
 import Bounds2 from '../../dot/js/Bounds2.js';
 import ScreenView from '../../joist/js/ScreenView.js';
-import Sim from '../../joist/js/Sim.js';
+import { UnknownSim } from '../../joist/js/Sim.js';
 import getGlobal from '../../phet-core/js/getGlobal.js';
 import optionize from '../../phet-core/js/optionize.js';
 import StrictOmit from '../../phet-core/js/types/StrictOmit.js';
@@ -129,7 +129,7 @@
   openedSoundPlayer?: TSoundPlayer;
   closedSoundPlayer?: TSoundPlayer;
 
-  sim?: Sim;
+  sim?: UnknownSim;
 
   // Called after the dialog is shown, see https://github.com/phetsims/joist/issues/478
   showCallback?: ( () => void ) | null;
@@ -145,7 +145,7 @@
 export default class Dialog extends Popupable( Panel, 1 ) {
 
   private readonly closeButton: CloseButton;
-  private readonly sim: Sim;
+  private readonly sim: UnknownSim;
   private readonly disposeDialog: () => void;
 
   /**
Index: main/sun/js/sun-main.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/main/sun/js/sun-main.ts b/main/sun/js/sun-main.ts
--- a/main/sun/js/sun-main.ts	(revision a8a5b6aea0640527cdced0a6d5115139421abf2d)
+++ b/main/sun/js/sun-main.ts	(date 1661801859358)
@@ -25,14 +25,12 @@
 
 simLauncher.launch( () => {
 
-  const screens = [
+  const sim = new Sim( sunStrings.sun.title, [
     new ButtonScreen( Tandem.ROOT.createTandem( 'buttonsScreen' ) ),
     new ComponentsScreen( Tandem.ROOT.createTandem( 'componentsScreen' ) ),
     new DialogsScreen( Tandem.ROOT.createTandem( 'dialogsScreen' ) ),
     new LayoutScreen( Tandem.ROOT.createTandem( 'layoutScreen' ) )
-  ];
-
-  const sim = new Sim( sunStrings.sun.title, screens, {
+  ], {
     credits: {
       leadDesign: 'PhET Interactive Simulations'
     },
Index: main/joist/js/ScreenshotGenerator.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/main/joist/js/ScreenshotGenerator.ts b/main/joist/js/ScreenshotGenerator.ts
--- a/main/joist/js/ScreenshotGenerator.ts	(revision 967a7adee65fefc039bffad000ae7726fc668b95)
+++ b/main/joist/js/ScreenshotGenerator.ts	(date 1661801688703)
@@ -10,11 +10,11 @@
 import Matrix3 from '../../dot/js/Matrix3.js';
 import { CanvasContextWrapper, Utils } from '../../scenery/js/imports.js';
 import joist from './joist.js';
-import Sim from './Sim.js';
+import Sim, { UnknownSim } from './Sim.js';
 
 class ScreenshotGenerator {
 
-  private static generateScreenshotAtIncreasedResolution( sim: Sim, scale: number ): HTMLCanvasElement {
+  private static generateScreenshotAtIncreasedResolution( sim: UnknownSim, scale: number ): HTMLCanvasElement {
     // set up our Canvas with the correct background color
     const canvas = document.createElement( 'canvas' );
     const context = canvas.getContext( '2d' )!;
@@ -47,7 +47,7 @@
   }
 
   // Default to PNG
-  public static generateScreenshot( sim: Sim, mimeType = 'image/png' ): string {
+  public static generateScreenshot( sim: UnknownSim, mimeType = 'image/png' ): string {
     const res2x = ScreenshotGenerator.generateScreenshotAtIncreasedResolution( sim, 2 );
     const res1x = ScreenshotGenerator.renderAtScale( res2x, 1 / 2 );
 
Index: main/geometric-optics/js/GOSim.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/main/geometric-optics/js/GOSim.ts b/main/geometric-optics/js/GOSim.ts
--- a/main/geometric-optics/js/GOSim.ts	(revision b7832067338cb8ed3dc00cf515667373ecd3d72d)
+++ b/main/geometric-optics/js/GOSim.ts	(date 1661800144425)
@@ -17,6 +17,10 @@
 import PickOptional from '../../phet-core/js/types/PickOptional.js';
 import PreferencesModel from '../../joist/js/preferences/PreferencesModel.js';
 import GOPreferences from './common/model/GOPreferences.js';
+import LensModel from './lens/model/LensModel.js';
+import LensScreenView from './lens/view/LensScreenView.js';
+import MirrorModel from './mirror/model/MirrorModel.js';
+import MirrorScreenView from './mirror/view/MirrorScreenView.js';
 
 type SelfOptions = {
 
@@ -28,9 +32,9 @@
 
 export type GOSimOptions = SelfOptions & PickOptional<SimOptions, 'phetioDesigned'>;
 
-export default class GOSim extends Sim {
+export default class GOSim {
 
-  public constructor( title: string, providedOptions: GOSimOptions ) {
+  public static create( title: string, providedOptions: GOSimOptions ) {
 
     const options = optionize<GOSimOptions, SelfOptions, SimOptions>()( {
 
@@ -54,7 +58,7 @@
       } )
     }, providedOptions );
 
-    super( title, [
+    return new Sim( title, [
       new LensScreen( {
         isBasicsVersion: options.isBasicsVersion,
         tandem: Tandem.ROOT.createTandem( 'lensScreen' )
Index: main/joist/js/HomeScreenButton.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/main/joist/js/HomeScreenButton.ts b/main/joist/js/HomeScreenButton.ts
--- a/main/joist/js/HomeScreenButton.ts	(revision 967a7adee65fefc039bffad000ae7726fc668b95)
+++ b/main/joist/js/HomeScreenButton.ts	(date 1661800856876)
@@ -24,7 +24,7 @@
 import Frame from './Frame.js';
 import HomeScreenModel from './HomeScreenModel.js';
 import joist from './joist.js';
-import Screen from './Screen.js';
+import Screen, { UnknownScreen } from './Screen.js';
 import Utterance from '../../utterance-queue/js/Utterance.js';
 
 // constants
@@ -37,9 +37,9 @@
 export type HomeScreenButtonOptions = SelfOptions & ParentOptions;
 
 class HomeScreenButton extends Voicing( VBox ) {
-  public readonly screen: Screen<IntentionalAny>;
+  public readonly screen: UnknownScreen;
 
-  public constructor( screen: Screen<IntentionalAny>, homeScreenModel: HomeScreenModel, providedOptions?: HomeScreenButtonOptions ) {
+  public constructor( screen: UnknownScreen, homeScreenModel: HomeScreenModel, providedOptions?: HomeScreenButtonOptions ) {
 
     const options = optionize<HomeScreenButtonOptions, SelfOptions, ParentOptions>()( {
       cursor: 'pointer',
Index: main/geometric-optics/js/geometric-optics-main.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/main/geometric-optics/js/geometric-optics-main.ts b/main/geometric-optics/js/geometric-optics-main.ts
--- a/main/geometric-optics/js/geometric-optics-main.ts	(revision b7832067338cb8ed3dc00cf515667373ecd3d72d)
+++ b/main/geometric-optics/js/geometric-optics-main.ts	(date 1661800221755)
@@ -11,7 +11,7 @@
 import GOSim from './GOSim.js';
 
 simLauncher.launch( () => {
-  const sim = new GOSim( geometricOpticsStrings[ 'geometric-optics' ].title, {
+  const sim = GOSim.create( geometricOpticsStrings[ 'geometric-optics' ].title, {
     isBasicsVersion: false,
     phetioDesigned: true
   } );
Index: main/joist/js/EngagementMetrics.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/main/joist/js/EngagementMetrics.ts b/main/joist/js/EngagementMetrics.ts
--- a/main/joist/js/EngagementMetrics.ts	(revision 967a7adee65fefc039bffad000ae7726fc668b95)
+++ b/main/joist/js/EngagementMetrics.ts	(date 1661800779109)
@@ -16,7 +16,7 @@
 import Property from '../../axon/js/Property.js';
 import IntentionalAny from '../../phet-core/js/types/IntentionalAny.js';
 import joist from './joist.js';
-import Sim from './Sim.js';
+import Sim, { UnknownSim } from './Sim.js';
 import TemporalCounter from './TemporalCounter.js';
 
 /////////////////////////////////
@@ -71,7 +71,7 @@
 
   private startTimestamp: number | null = null; // number, the timestamp of the start of the sim.
 
-  public constructor( sim: Sim ) {
+  public constructor( sim: UnknownSim ) {
     const dataStream = phet && phet.phetio && phet.phetio.dataStream;
     assert && assert( dataStream, 'cannot add dataStream listener because dataStream is not defined' );
 
Index: main/number-play/js/number-play-main.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/main/number-play/js/number-play-main.ts b/main/number-play/js/number-play-main.ts
--- a/main/number-play/js/number-play-main.ts	(revision 1462a16b629bf45dff11340806d116d6859b0958)
+++ b/main/number-play/js/number-play-main.ts	(date 1661801739588)
@@ -21,7 +21,7 @@
 import DerivedProperty from '../../axon/js/DerivedProperty.js';
 import audioManager from '../../joist/js/audioManager.js';
 import SpeechSynthesisAnnouncer from '../../utterance-queue/js/SpeechSynthesisAnnouncer.js';
-import Screen from '../../joist/js/Screen.js';
+import Screen, { UnknownScreen } from '../../joist/js/Screen.js';
 import soundManager from '../../tambo/js/soundManager.js';
 import NumberPlayModel from './common/model/NumberPlayModel.js';
 
@@ -89,7 +89,7 @@
   // screen has its own control for the speech synthesis locale, so the locale for the browser tab needs to be updated
   // to match whenever the screen changes.
   if ( NumberPlayQueryParameters.secondLocale ) {
-    sim.selectedScreenProperty.lazyLink( ( screen: Screen ) => {
+    sim.selectedScreenProperty.lazyLink( ( screen: UnknownScreen ) => {
 
       if ( screen.model instanceof NumberPlayModel &&
            numberPlaySpeechSynthesisAnnouncer.initialized && screen.model.isPrimaryLocaleProperty ) {
Index: main/nitroglycerin/js/nitroglycerin-main.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/main/nitroglycerin/js/nitroglycerin-main.ts b/main/nitroglycerin/js/nitroglycerin-main.ts
--- a/main/nitroglycerin/js/nitroglycerin-main.ts	(revision 008b9987999a9d894360c10b5724bf95816dfbdc)
+++ b/main/nitroglycerin/js/nitroglycerin-main.ts	(date 1661801729849)
@@ -23,21 +23,19 @@
 
 simLauncher.launch( () => {
 
-  const screens = [
+  const simOptions: SimOptions = {
+    credits: {
+      leadDesign: 'PhET'
+    }
+  };
+
+  const sim = new Sim( title, [
     new Screen(
       () => new Model(),
       () => new MoleculesScreenView(), {
         name: 'Molecules',
         backgroundColorProperty: new Property( Color.grayColor( 90 ) ),
         tandem: Tandem.OPT_OUT
-      } ) ];
-
-  const simOptions: SimOptions = {
-    credits: {
-      leadDesign: 'PhET'
-    }
-  };
-
-  const sim = new Sim( title, screens, simOptions );
+      } ) ], simOptions );
   sim.start();
 } );
\ No newline at end of file
Index: main/build-a-nucleus/js/common/model/BANModel.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/main/build-a-nucleus/js/common/model/BANModel.ts b/main/build-a-nucleus/js/common/model/BANModel.ts
--- a/main/build-a-nucleus/js/common/model/BANModel.ts	(revision b9e709d83409da09444acf5a5811dc32657bc0c0)
+++ b/main/build-a-nucleus/js/common/model/BANModel.ts	(date 1661800393015)
@@ -22,11 +22,12 @@
 import ParticleType from '../view/ParticleType.js';
 import BooleanProperty from '../../../../axon/js/BooleanProperty.js';
 import Animation from '../../../../twixt/js/Animation.js';
+import TModel from '../../../../joist/js/TModel.js';
 
 // types
 export type BANModelOptions = PickRequired<PhetioObjectOptions, 'tandem'>;
 
-class BANModel {
+class BANModel implements TModel {
 
   // the stability of the nuclide
   public readonly isStableBooleanProperty: TReadOnlyProperty<boolean>;
Index: main/simula-rasa/js/simula-rasa-main.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/main/simula-rasa/js/simula-rasa-main.ts b/main/simula-rasa/js/simula-rasa-main.ts
--- a/main/simula-rasa/js/simula-rasa-main.ts	(revision d4fcef83b53e955b02cafd28c49b4713f326ff67)
+++ b/main/simula-rasa/js/simula-rasa-main.ts	(date 1661801826345)
@@ -19,10 +19,6 @@
 
   const title = simulaRasaStrings[ 'simula-rasa' ].title;
 
-  const screens = [
-    new SimulaRasaScreen( { tandem: Tandem.ROOT.createTandem( 'simulaRasaScreen' ) } )
-  ];
-
   const options: SimOptions = {
 
     //TODO fill in credits, all of these fields are optional, see joist.CreditsNode
@@ -38,6 +34,8 @@
     }
   };
 
-  const sim = new Sim( title, screens, options );
+  const sim = new Sim( title, [
+    new SimulaRasaScreen( { tandem: Tandem.ROOT.createTandem( 'simulaRasaScreen' ) } )
+  ], options );
   sim.start();
 } );
\ No newline at end of file
Index: main/joist/js/Profiler.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/main/joist/js/Profiler.ts b/main/joist/js/Profiler.ts
--- a/main/joist/js/Profiler.ts	(revision 967a7adee65fefc039bffad000ae7726fc668b95)
+++ b/main/joist/js/Profiler.ts	(date 1661801555578)
@@ -31,7 +31,7 @@
 
 import Utils from '../../dot/js/Utils.js';
 import joist from './joist.js';
-import Sim from './Sim.js';
+import Sim, { UnknownSim } from './Sim.js';
 
 // constants
 const FIELD_SEPARATOR = ' \u2014 '; // em dash, a long horizontal dash
@@ -58,7 +58,7 @@
     $( 'body' ).append( '<div style="z-index: 99999999;position: absolute;color:red" id="phetProfiler" ></div>' );
   }
 
-  public static start( sim: Sim ): void {
+  public static start( sim: UnknownSim ): void {
     const profiler = new Profiler();
     sim.frameStartedEmitter.addListener( () => profiler.frameStarted() );
     sim.frameEndedEmitter.addListener( () => profiler.frameEnded() );
Index: main/joist/js/Sim.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/main/joist/js/Sim.ts b/main/joist/js/Sim.ts
--- a/main/joist/js/Sim.ts	(revision 967a7adee65fefc039bffad000ae7726fc668b95)
+++ b/main/joist/js/Sim.ts	(date 1661803205697)
@@ -51,7 +51,7 @@
 import PreferencesModel from './preferences/PreferencesModel.js';
 import Profiler from './Profiler.js';
 import QueryParametersWarningDialog from './QueryParametersWarningDialog.js';
-import Screen from './Screen.js';
+import Screen, { UnknownScreen, UnknownSimScreen } from './Screen.js';
 import ScreenSelectionSoundGenerator from './ScreenSelectionSoundGenerator.js';
 import ScreenshotGenerator from './ScreenshotGenerator.js';
 import selectScreens from './selectScreens.js';
@@ -70,6 +70,8 @@
 import Permutation from '../../dot/js/Permutation.js';
 import ArrayIO from '../../tandem/js/types/ArrayIO.js';
 import StringIO from '../../tandem/js/types/StringIO.js';
+import TModel from './TModel.js';
+import ScreenView from './ScreenView.js';
 
 // constants
 const PROGRESS_BAR_WIDTH = 273;
@@ -108,7 +110,14 @@
 
 export type SimOptions = SelfOptions & PickOptional<PhetioObject, 'phetioDesigned'>;
 
-export default class Sim extends PhetioObject {
+export type UnknownSim = Sim<TModel, ScreenView, TModel, ScreenView, TModel, ScreenView, TModel, ScreenView, TModel, ScreenView, TModel, ScreenView>;
+
+export default class Sim<M1 extends TModel, V1 extends ScreenView,
+  M2 extends TModel, V2 extends ScreenView,
+  M3 extends TModel, V3 extends ScreenView,
+  M4 extends TModel, V4 extends ScreenView,
+  M5 extends TModel, V5 extends ScreenView,
+  M6 extends TModel, V6 extends ScreenView> extends PhetioObject {
 
   // (joist-internal)
   public readonly simNameProperty: TReadOnlyProperty<string>;
@@ -150,20 +159,20 @@
   public readonly stepSimulationAction: PhetioAction<[ number ]>;
 
   // the ordered list of sim-specific screens that appear in this runtime of the sim
-  public readonly simScreens: Screen[];
+  public readonly simScreens: UnknownScreen[];
 
   // all screens that appear in the runtime of this sim, with the homeScreen first if it was created
-  public readonly screens: Screen[];
+  public readonly screens: UnknownScreen[];
 
   // the displayed name in the sim. This depends on what screens are shown this runtime (effected by query parameters).
   public readonly displayedSimNameProperty: TReadOnlyProperty<string>;
-  public readonly selectedScreenProperty: Property<Screen>;
+  public readonly selectedScreenProperty: Property<Screen<M1, V1> | Screen<M2, V2> | Screen<M3, V3> | Screen<M4, V4> | Screen<M5, V5> | Screen<M6, V6> | HomeScreen>;
 
   // true if all possible screens are present (order-independent)
   private readonly allScreensCreated: boolean;
 
   private availableScreensProperty!: Property<number[]>;
-  public activeSimScreensProperty!: ReadOnlyProperty<Screen[]>;
+  public activeSimScreensProperty!: ReadOnlyProperty<( Screen<M1, V1> | Screen<M2, V2> | Screen<M3, V3> | Screen<M4, V4> | Screen<M5, V5> | Screen<M6, V6> )[]>;
 
   // When the sim is active, scenery processes inputs and stepSimulation(dt) runs from the system clock.
   // Set to false for when the sim will be paused.
@@ -257,7 +266,13 @@
    * @param allSimScreens - the possible screens for the sim in order of declaration (does not include the home screen)
    * @param [providedOptions] - see below for options
    */
-  public constructor( name: string | TReadOnlyProperty<string>, allSimScreens: Screen[], providedOptions?: SimOptions ) {
+  public constructor( name: string | TReadOnlyProperty<string>, allSimScreens: [ Screen<M1, V1> ], providedOptions?: SimOptions );
+  public constructor( name: string | TReadOnlyProperty<string>, allSimScreens: [ Screen<M1, V1> ] | [ Screen<M1, V1>, Screen<M2, V2> ], providedOptions?: SimOptions );
+  public constructor( name: string | TReadOnlyProperty<string>, allSimScreens: [ Screen<M1, V1> ] | [ Screen<M1, V1>, Screen<M2, V2> ] | [ Screen<M1, V1>, Screen<M2, V2>, Screen<M3, V3> ], providedOptions?: SimOptions );
+  public constructor( name: string | TReadOnlyProperty<string>, allSimScreens: [ Screen<M1, V1> ] | [ Screen<M1, V1>, Screen<M2, V2> ] | [ Screen<M1, V1>, Screen<M2, V2>, Screen<M3, V3> ] | [ Screen<M1, V1>, Screen<M2, V2>, Screen<M3, V3>, Screen<M4, V4> ], providedOptions?: SimOptions );
+  public constructor( name: string | TReadOnlyProperty<string>, allSimScreens: [ Screen<M1, V1> ] | [ Screen<M1, V1>, Screen<M2, V2> ] | [ Screen<M1, V1>, Screen<M2, V2>, Screen<M3, V3> ] | [ Screen<M1, V1>, Screen<M2, V2>, Screen<M3, V3>, Screen<M4, V4> ] | [ Screen<M1, V1>, Screen<M2, V2>, Screen<M3, V3>, Screen<M4, V4>, Screen<M5, V5> ], providedOptions?: SimOptions );
+  public constructor( name: string | TReadOnlyProperty<string>, allSimScreens: [ Screen<M1, V1> ] | [ Screen<M1, V1>, Screen<M2, V2> ] | [ Screen<M1, V1>, Screen<M2, V2>, Screen<M3, V3> ] | [ Screen<M1, V1>, Screen<M2, V2>, Screen<M3, V3>, Screen<M4, V4> ] | [ Screen<M1, V1>, Screen<M2, V2>, Screen<M3, V3>, Screen<M4, V4>, Screen<M5, V5> ] | [ Screen<M1, V1>, Screen<M2, V2>, Screen<M3, V3>, Screen<M4, V4>, Screen<M5, V5>, Screen<M6, V6> ], providedOptions?: SimOptions );
+  public constructor( name: string | TReadOnlyProperty<string>, allSimScreens: [ Screen<M1, V1> ] | [ Screen<M1, V1>, Screen<M2, V2> ] | [ Screen<M1, V1>, Screen<M2, V2>, Screen<M3, V3> ] | [ Screen<M1, V1>, Screen<M2, V2>, Screen<M3, V3>, Screen<M4, V4> ] | [ Screen<M1, V1>, Screen<M2, V2>, Screen<M3, V3>, Screen<M4, V4>, Screen<M5, V5> ] | [ Screen<M1, V1>, Screen<M2, V2>, Screen<M3, V3>, Screen<M4, V4>, Screen<M5, V5>, Screen<M6, V6> ], providedOptions?: SimOptions ) {
 
     window.phetSplashScreenDownloadComplete();
 
@@ -478,7 +493,7 @@
 
     const screensTandem = Tandem.GENERAL_MODEL.createTandem( 'screens' );
 
-    const screenData = selectScreens(
+    const screenData = selectScreens<M1, V1, M2, V2, M3, V3, M4, V4, M5, V5, M6, V6>(
       allSimScreens,
       phet.chipper.queryParameters.homeScreen,
       QueryStringMachine.containsKey( 'homeScreen' ),
@@ -520,7 +535,7 @@
     this.screens = screenData.screens;
     this.allScreensCreated = screenData.allScreensCreated;
 
-    this.selectedScreenProperty = new Property<Screen>( screenData.initialScreen, {
+    this.selectedScreenProperty = new Property( screenData.initialScreen, {
       tandem: screensTandem.createTandem( 'selectedScreenProperty' ),
       phetioFeatured: true,
       phetioDocumentation: 'Determines which screen is selected in the simulation',
@@ -729,7 +744,7 @@
     animationFrameTimer.runOnNextTick( () => phet.joist.display.updateDisplay() );
   }
 
-  private finishInit( screens: Screen[] ): void {
+  private finishInit( screens: UnknownScreen[] ): void {
 
     _.each( screens, screen => {
       screen.view.layerSplit = true;
Index: main/joist/js/PhetMenu.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/main/joist/js/PhetMenu.ts b/main/joist/js/PhetMenu.ts
--- a/main/joist/js/PhetMenu.ts	(revision 967a7adee65fefc039bffad000ae7726fc668b95)
+++ b/main/joist/js/PhetMenu.ts	(date 1661801548543)
@@ -26,7 +26,7 @@
 import joist from './joist.js';
 import joistStrings from './joistStrings.js';
 import ScreenshotGenerator from './ScreenshotGenerator.js';
-import Sim from './Sim.js';
+import Sim, { UnknownSim } from './Sim.js';
 import updateCheck from './updateCheck.js';
 import UpdateDialog from './UpdateDialog.js';
 import UpdateState from './UpdateState.js';
@@ -63,7 +63,7 @@
 
   private readonly disposePhetMenu: () => void;
 
-  public constructor( sim: Sim, providedOptions?: PhetMenuOptions ) {
+  public constructor( sim: UnknownSim, providedOptions?: PhetMenuOptions ) {
 
     // Only show certain features for PhET Sims, such as links to our website
     const isPhETBrand = phet.chipper.brand === 'phet';
Index: main/joist/js/joist-main.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/main/joist/js/joist-main.ts b/main/joist/js/joist-main.ts
--- a/main/joist/js/joist-main.ts	(revision 967a7adee65fefc039bffad000ae7726fc668b95)
+++ b/main/joist/js/joist-main.ts	(date 1661801882975)
@@ -28,7 +28,7 @@
 
 simLauncher.launch( () => {
 
-  const screens = [
+  new Sim( joistTitleString, [
     new Screen(
       ( () => new DemoModel() ),
       ( () => new DialogsScreenView() ), {
@@ -36,7 +36,5 @@
         backgroundColorProperty: new Property( 'white' ),
         tandem: Tandem.OPT_OUT
       } )
-  ];
-
-  new Sim( joistTitleString, screens, simOptions ).start();
+  ], simOptions ).start();
 } );
\ No newline at end of file
Index: main/joist/js/A11yButtonsHBox.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/main/joist/js/A11yButtonsHBox.ts b/main/joist/js/A11yButtonsHBox.ts
--- a/main/joist/js/A11yButtonsHBox.ts	(revision 967a7adee65fefc039bffad000ae7726fc668b95)
+++ b/main/joist/js/A11yButtonsHBox.ts	(date 1661800676839)
@@ -14,7 +14,7 @@
 import KeyboardHelpButton from './KeyboardHelpButton.js';
 import NavigationBarAudioToggleButton from './NavigationBarAudioToggleButton.js';
 import NavigationBarPreferencesButton from './preferences/NavigationBarPreferencesButton.js';
-import Sim from './Sim.js';
+import { UnknownSim } from './Sim.js';
 import TReadOnlyProperty from '../../axon/js/TReadOnlyProperty.js';
 import optionize, { EmptySelfOptions } from '../../phet-core/js/optionize.js';
 import StrictOmit from '../../phet-core/js/types/StrictOmit.js';
@@ -24,7 +24,7 @@
 
 class A11yButtonsHBox extends HBox {
 
-  public constructor( sim: Sim, backgroundColorProperty: TReadOnlyProperty<Color>, providedOptions?: A11yButtonsHBoxOptions ) {
+  public constructor( sim: UnknownSim, backgroundColorProperty: TReadOnlyProperty<Color>, providedOptions?: A11yButtonsHBoxOptions ) {
 
     const options = optionize<A11yButtonsHBoxOptions, SelfOptions, HBoxOptions>()( {
       align: 'center',
Index: main/joist/js/HomeScreen.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/main/joist/js/HomeScreen.ts b/main/joist/js/HomeScreen.ts
--- a/main/joist/js/HomeScreen.ts	(revision 967a7adee65fefc039bffad000ae7726fc668b95)
+++ b/main/joist/js/HomeScreen.ts	(date 1661801307345)
@@ -16,7 +16,9 @@
 import HomeScreenView from './HomeScreenView.js';
 import joist from './joist.js';
 import joistStrings from './joistStrings.js';
-import Screen, { ScreenOptions } from './Screen.js';
+import Screen, { ScreenOptions, UnknownScreen } from './Screen.js';
+import TModel from './TModel.js';
+import ScreenView from './ScreenView.js';
 
 // constants
 const homeString = joistStrings.a11y.home;
@@ -32,9 +34,9 @@
 
   public constructor(
     simNameProperty: TReadOnlyProperty<string>,
-    getScreenProperty: () => Property<Screen>,
-    simScreens: Screen[],
-    activeSimScreensProperty: ReadOnlyProperty<Screen[]>,
+    getScreenProperty: () => Property<UnknownScreen>,
+    simScreens: UnknownScreen[],
+    activeSimScreensProperty: ReadOnlyProperty<UnknownScreen[]>,
     providedOptions: HomeScreenOptions
   ) {
 
Index: main/joist/js/demo/DialogsScreenView.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/main/joist/js/demo/DialogsScreenView.ts b/main/joist/js/demo/DialogsScreenView.ts
--- a/main/joist/js/demo/DialogsScreenView.ts	(revision 967a7adee65fefc039bffad000ae7726fc668b95)
+++ b/main/joist/js/demo/DialogsScreenView.ts	(date 1661800779116)
@@ -14,20 +14,20 @@
 import joist from '../joist.js';
 import KeyboardHelpButton from '../KeyboardHelpButton.js';
 import ScreenView from '../ScreenView.js';
-import Screen from '../Screen.js';
-import Sim from '../Sim.js';
+import Screen, { UnknownScreen } from '../Screen.js';
+import Sim, { UnknownSim } from '../Sim.js';
 
 class DialogsScreenView extends ScreenView {
   public constructor() {
 
     super();
 
-    const sim = phet.joist.sim as Sim;
+    const sim = phet.joist.sim as UnknownSim;
 
     const keyboardHelpDialogContent = new BasicActionsKeyboardHelpSection();
 
     const keyboardHelpButton = new KeyboardHelpButton(
-      new Property( { keyboardHelpNode: keyboardHelpDialogContent } as unknown as Screen ),
+      new Property( { keyboardHelpNode: keyboardHelpDialogContent } as unknown as UnknownScreen ),
       sim.lookAndFeel.navigationBarFillProperty, {
         tandem: Tandem.GENERAL_VIEW.createTandem( 'keyboardHelpButton' )
       } );
Index: main/joist/js/toolbar/Toolbar.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/main/joist/js/toolbar/Toolbar.ts b/main/joist/js/toolbar/Toolbar.ts
--- a/main/joist/js/toolbar/Toolbar.ts	(revision 967a7adee65fefc039bffad000ae7726fc668b95)
+++ b/main/joist/js/toolbar/Toolbar.ts	(date 1661801698782)
@@ -32,7 +32,7 @@
 import VoicingToolbarAlertManager from './VoicingToolbarAlertManager.js';
 import VoicingToolbarItem from './VoicingToolbarItem.js';
 import LookAndFeel from '../LookAndFeel.js';
-import Screen from '../Screen.js';
+import Screen, { UnknownScreen } from '../Screen.js';
 
 // constants
 const MAX_ANIMATION_SPEED = 250; // in view coordinates per second, assuming 60 fps
@@ -91,7 +91,7 @@
   private readonly contentMargin: number;
   private readonly disposeToolbar: () => void;
 
-  public constructor( enabledProperty: TReadOnlyProperty<boolean>, selectedScreenProperty: TReadOnlyProperty<Screen>,
+  public constructor( enabledProperty: TReadOnlyProperty<boolean>, selectedScreenProperty: TReadOnlyProperty<UnknownScreen>,
                       lookAndFeel: LookAndFeel, providedOptions?: ToolbarOptions ) {
 
     const options = combineOptions<ToolbarOptions>( {
Index: main/joist/js/Helper.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/main/joist/js/Helper.ts b/main/joist/js/Helper.ts
--- a/main/joist/js/Helper.ts	(revision 967a7adee65fefc039bffad000ae7726fc668b95)
+++ b/main/joist/js/Helper.ts	(date 1661801155476)
@@ -20,7 +20,7 @@
 import AquaRadioButtonGroup from '../../sun/js/AquaRadioButtonGroup.js';
 import Tandem from '../../tandem/js/Tandem.js';
 import joist from './joist.js';
-import Sim from './Sim.js';
+import Sim, { UnknownSim } from './Sim.js';
 import SimDisplay from './SimDisplay.js';
 import BooleanProperty from '../../axon/js/BooleanProperty.js';
 import Checkbox, { CheckboxOptions } from '../../sun/js/Checkbox.js';
@@ -61,7 +61,7 @@
 };
 
 export default class Helper {
-  private sim: Sim;
+  private sim: UnknownSim;
   private simDisplay: Display;
   private helperDisplay?: Display;
 
@@ -123,7 +123,7 @@
   // The pixel color under the pointer
   public colorProperty: TReadOnlyProperty<Color>;
 
-  public constructor( sim: Sim, simDisplay: SimDisplay ) {
+  public constructor( sim: UnknownSim, simDisplay: SimDisplay ) {
 
     // NOTE: Don't pause the sim, don't use foreign object rasterization (do the smarter instant approach)
     // NOTE: Inform about preserveDrawingBuffer query parameter
@@ -860,7 +860,7 @@
   // Singleton, lazily created so we don't slow down startup
   public static helper?: Helper;
 
-  public static initialize( sim: Sim, simDisplay: SimDisplay ): void {
+  public static initialize( sim: UnknownSim, simDisplay: SimDisplay ): void {
     // Ctrl + shift + H (will open the helper)
     document.addEventListener( 'keydown', ( event: KeyboardEvent ) => {
       if ( event.ctrlKey && event.key === 'H' ) {
Index: main/quadrilateral/js/quadrilateral-main.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/main/quadrilateral/js/quadrilateral-main.ts b/main/quadrilateral/js/quadrilateral-main.ts
--- a/main/quadrilateral/js/quadrilateral-main.ts	(revision cf9b4377a2707984672d49ede787a92b3c724115)
+++ b/main/quadrilateral/js/quadrilateral-main.ts	(date 1661801942123)
@@ -62,9 +62,8 @@
     backgroundColorProperty: new ColorProperty( new Color( 'white' ) ),
     tandem: Tandem.ROOT.createTandem( 'calibrationDemoScreen' )
   } );
-  const simScreens = QuadrilateralQueryParameters.calibrationDemo ? [ quadrilateralScreen, calibrationDemoScreen ] : [ quadrilateralScreen ];
 
-  const sim = new Sim( quadrilateralTitleString, simScreens, simOptions );
+  const sim = new Sim( quadrilateralTitleString, QuadrilateralQueryParameters.calibrationDemo ? [ quadrilateralScreen, calibrationDemoScreen ] : [ quadrilateralScreen ], simOptions );
   sim.start();
 
   // @ts-ignore
Index: main/scenery-phet/js/scenery-phet-main.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/main/scenery-phet/js/scenery-phet-main.ts b/main/scenery-phet/js/scenery-phet-main.ts
--- a/main/scenery-phet/js/scenery-phet-main.ts	(revision c380dd8234f656881282af5ad5c7c42431b37c7d)
+++ b/main/scenery-phet/js/scenery-phet-main.ts	(date 1661801750961)
@@ -34,16 +34,14 @@
 // Create and start sim
 simLauncher.launch( () => {
 
-  const screens = [
+  const sim = new Sim( sceneryPhetStrings[ 'scenery-phet' ].title, [
     new ButtonsScreen( Tandem.ROOT.createTandem( 'buttonsScreen' ) ),
     new ComponentsScreen( Tandem.ROOT.createTandem( 'componentsScreen' ) ),
     new DialogsScreen( Tandem.ROOT.createTandem( 'dialogsScreen' ) ),
     new KeyboardScreen( Tandem.ROOT.createTandem( 'keyboardScreen' ) ),
     new SlidersScreen( Tandem.ROOT.createTandem( 'slidersScreen' ) ),
     new SpinnersScreen( Tandem.ROOT.createTandem( 'spinnersScreen' ) )
-  ];
-
-  const sim = new Sim( sceneryPhetStrings[ 'scenery-phet' ].title, screens, {
+  ], {
     credits: {
       leadDesign: 'PhET'
     },
Index: main/joist/js/selectScreensTests.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/main/joist/js/selectScreensTests.ts b/main/joist/js/selectScreensTests.ts
--- a/main/joist/js/selectScreensTests.ts	(revision 967a7adee65fefc039bffad000ae7726fc668b95)
+++ b/main/joist/js/selectScreensTests.ts	(date 1661801253711)
@@ -9,13 +9,13 @@
  */
 
 import selectScreens, { ScreenReturnType } from './selectScreens.js';
-import Screen from './Screen.js';
+import Screen, { UnknownScreen } from './Screen.js';
 import HomeScreen from './HomeScreen.js';
 
 // test screen constants. Since these are tests, it is actually more valuable to typecast instead of making these actual screens.
-const a = 'a' as unknown as Screen;
-const b = 'b' as unknown as Screen;
-const c = 'c' as unknown as Screen;
+const a = 'a' as unknown as UnknownScreen;
+const b = 'b' as unknown as UnknownScreen;
+const c = 'c' as unknown as UnknownScreen;
 const hs = 'hs' as unknown as HomeScreen;
 
 const getQueryParameterValues = ( queryString: string ) => {
@@ -56,7 +56,7 @@
 /**
  * Format the query string + all sim screens to uniquely identify the test.
  */
-const getDescription = ( queryString: string, allSimScreens: Screen[] ): string => `${queryString} ${JSON.stringify( allSimScreens )}`;
+const getDescription = ( queryString: string, allSimScreens: UnknownScreen[] ): string => `${queryString} ${JSON.stringify( allSimScreens )}`;
 
 QUnit.test( 'valid selectScreens', async assert => {
 
@@ -64,7 +64,7 @@
    * Tests a valid combination of allSimScreens and screens-related query parameters, where the expectedResult should
    * equal the result returned from ScreenSelector.select
    */
-  const testValidScreenSelector = ( queryString: string, allSimScreens: Screen[], expectedResult: ScreenReturnType ) => {
+  const testValidScreenSelector = ( queryString: string, allSimScreens: UnknownScreen[], expectedResult: ScreenReturnType ) => {
     const queryParameterValues = getQueryParameterValues( queryString );
 
     const result = selectScreens(
@@ -228,7 +228,7 @@
    * Tests an invalid combination of allSimScreens and screens-related query parameters, where selectScreens should
    * throw an error
    */
-  const testInvalidScreenSelector = ( queryString: string, allSimScreens: Screen[] ) => {
+  const testInvalidScreenSelector = ( queryString: string, allSimScreens: UnknownScreen[] ) => {
     const queryParameterValues = getQueryParameterValues( queryString );
     const description = getDescription( queryString, allSimScreens );
 
Index: main/joist/js/Screen.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/main/joist/js/Screen.ts b/main/joist/js/Screen.ts
--- a/main/joist/js/Screen.ts	(revision 967a7adee65fefc039bffad000ae7726fc668b95)
+++ b/main/joist/js/Screen.ts	(date 1661802291886)
@@ -35,6 +35,8 @@
 import Multilink from '../../axon/js/Multilink.js';
 import TModel from './TModel.js';
 import ReadOnlyProperty from '../../axon/js/ReadOnlyProperty.js';
+import HomeScreenModel from './HomeScreenModel.js';
+import HomeScreenView from './HomeScreenView.js';
 
 const screenNamePatternString = joistStrings.a11y.screenNamePattern;
 const screenSimPatternString = joistStrings.a11y.screenSimPattern;
@@ -69,12 +71,11 @@
 };
 export type ScreenOptions = SelfOptions & PhetioObjectOptions & PickRequired<PhetioObjectOptions, 'tandem'>;
 
-// Accept any subtype of TModel (defaults to supertype), and any subtype of ScreenView (defaults to subtype).
-// @ts-ignore
-type CreateView<out M extends TModel, V> = ( model: M ) => V;
+export type UnknownScreen = Screen<TModel, ScreenView> | Screen<HomeScreenModel, HomeScreenView>;
+export type UnknownSimScreen = Screen<TModel, ScreenView>;
 
 // Parameterized on M=Model and V=View
-class Screen<M extends TModel = TModel, V extends ScreenView = ScreenView> extends PhetioObject {
+class Screen<M extends TModel, V extends ScreenView> extends PhetioObject {
 
   public backgroundColorProperty: Property<Color> | Property<string> | Property<Color | string>;
 
@@ -90,7 +91,7 @@
   public readonly keyboardHelpNode: Node | null; // joist-internal
   public readonly pdomDisplayNameProperty: TReadOnlyProperty<string | null>;
   private readonly createModel: () => M;
-  private readonly createView: CreateView<M, V>;
+  private readonly createView: ( m: M ) => V;
   private _model: M | null;
   private _view: V | null;
 
@@ -99,7 +100,7 @@
   public static MINIMUM_NAVBAR_ICON_SIZE: Dimension2;
   public static ScreenIO: IOType;
 
-  public constructor( createModel: () => M, createView: CreateView<M, V>, providedOptions: ScreenOptions ) {
+  public constructor( createModel: () => M, createView: ( m: M ) => V, providedOptions: ScreenOptions ) {
 
     const options = optionize<ScreenOptions, SelfOptions, PhetioObjectOptions>()( {
 
Index: main/wave-interference/js/diffraction/DiffractionScreen.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/main/wave-interference/js/diffraction/DiffractionScreen.ts b/main/wave-interference/js/diffraction/DiffractionScreen.ts
--- a/main/wave-interference/js/diffraction/DiffractionScreen.ts	(revision 19d020ea56b6652cfa0aefb9c2b0990fada24fbf)
+++ b/main/wave-interference/js/diffraction/DiffractionScreen.ts	(date 1661801054504)
@@ -19,7 +19,7 @@
 
 const screenDiffractionString = waveInterferenceStrings.screen.diffraction;
 
-class DiffractionScreen extends Screen {
+class DiffractionScreen extends Screen<DiffractionModel, DiffractionScreenView> {
 
   public constructor() {
     const options = {
Index: main/joist/js/NavigationBarScreenButton.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/main/joist/js/NavigationBarScreenButton.ts b/main/joist/js/NavigationBarScreenButton.ts
--- a/main/joist/js/NavigationBarScreenButton.ts	(revision 967a7adee65fefc039bffad000ae7726fc668b95)
+++ b/main/joist/js/NavigationBarScreenButton.ts	(date 1661800884695)
@@ -23,7 +23,7 @@
 import Tandem from '../../tandem/js/Tandem.js';
 import HighlightNode from './HighlightNode.js';
 import joist from './joist.js';
-import Screen from './Screen.js';
+import Screen, { UnknownScreen } from './Screen.js';
 
 // constants
 const HIGHLIGHT_SPACING = 4;
@@ -38,7 +38,7 @@
 class NavigationBarScreenButton extends Voicing( Node ) {
   private readonly buttonModel: PushButtonModel;
 
-  public readonly screen: Screen<IntentionalAny>;
+  public readonly screen: UnknownScreen;
 
   /**
    * @param navigationBarFillProperty - the color of the navbar, as a string.
@@ -48,8 +48,8 @@
    * @param navBarHeight
    * @param [providedOptions]
    */
-  public constructor( navigationBarFillProperty: TReadOnlyProperty<Color>, screenProperty: Property<Screen<IntentionalAny>>,
-                      screen: Screen<IntentionalAny>, simScreenIndex: number, navBarHeight: number,
+  public constructor( navigationBarFillProperty: TReadOnlyProperty<Color>, screenProperty: Property<UnknownScreen>,
+                      screen: UnknownScreen, simScreenIndex: number, navBarHeight: number,
                       providedOptions: NavigationBarScreenButtonOptions ) {
 
     assert && assert( screen.nameProperty.value, `name is required for screen ${simScreenIndex}` );
Index: main/wave-interference/js/interference/InterferenceScreen.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/main/wave-interference/js/interference/InterferenceScreen.ts b/main/wave-interference/js/interference/InterferenceScreen.ts
--- a/main/wave-interference/js/interference/InterferenceScreen.ts	(revision 19d020ea56b6652cfa0aefb9c2b0990fada24fbf)
+++ b/main/wave-interference/js/interference/InterferenceScreen.ts	(date 1661802221365)
@@ -19,7 +19,7 @@
 
 const screenInterferenceString = waveInterferenceStrings.screen.interference;
 
-class InterferenceScreen extends Screen {
+class InterferenceScreen extends Screen<InterferenceModel, InterferenceScreenView> {
 
   /**
    * @param alignGroup - for aligning the control panels on the right side of the lattice
Index: main/models-of-the-hydrogen-atom/js/models-of-the-hydrogen-atom-main.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/main/models-of-the-hydrogen-atom/js/models-of-the-hydrogen-atom-main.ts b/main/models-of-the-hydrogen-atom/js/models-of-the-hydrogen-atom-main.ts
--- a/main/models-of-the-hydrogen-atom/js/models-of-the-hydrogen-atom-main.ts	(revision f02cdfd6b7285e7a9daf5730ff46f950546ac9d2)
+++ b/main/models-of-the-hydrogen-atom/js/models-of-the-hydrogen-atom-main.ts	(date 1661801720228)
@@ -18,11 +18,6 @@
 
   const title = modelsOfTheHydrogenAtomStrings[ 'models-of-the-hydrogen-atom' ].title;
 
-  const screens = [
-    new SpectraScreen( { tandem: Tandem.ROOT.createTandem( 'spectraScreen' ) } ),
-    new EnergyLevelsScreen( { tandem: Tandem.ROOT.createTandem( 'energyLevelsScreen' ) } )
-  ];
-
   const options: SimOptions = {
 
 
@@ -42,6 +37,9 @@
     }
   };
 
-  const sim = new Sim( title, screens, options );
+  const sim = new Sim( title, [
+    new SpectraScreen( { tandem: Tandem.ROOT.createTandem( 'spectraScreen' ) } ),
+    new EnergyLevelsScreen( { tandem: Tandem.ROOT.createTandem( 'energyLevelsScreen' ) } )
+  ], options );
   sim.start();
 } );
\ No newline at end of file
Index: main/wave-interference/js/common/BaseScreen.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/main/wave-interference/js/common/BaseScreen.ts b/main/wave-interference/js/common/BaseScreen.ts
--- a/main/wave-interference/js/common/BaseScreen.ts	(revision 19d020ea56b6652cfa0aefb9c2b0990fada24fbf)
+++ b/main/wave-interference/js/common/BaseScreen.ts	(date 1661802185458)
@@ -22,7 +22,7 @@
 };
 export type BaseScreenOptions = SelfOptions & ScreenOptions;
 
-class BaseScreen extends Screen {
+class BaseScreen extends Screen<WavesModel, WavesScreenView> {
 
   /**
    * @param alignGroup - for aligning the control panels on the right side of the lattice
Index: main/joist/js/KeyboardHelpButton.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/main/joist/js/KeyboardHelpButton.ts b/main/joist/js/KeyboardHelpButton.ts
--- a/main/joist/js/KeyboardHelpButton.ts	(revision 967a7adee65fefc039bffad000ae7726fc668b95)
+++ b/main/joist/js/KeyboardHelpButton.ts	(date 1661800868529)
@@ -18,7 +18,7 @@
 import JoistButton, { JoistButtonOptions } from './JoistButton.js';
 import joistStrings from './joistStrings.js';
 import KeyboardHelpDialog from './KeyboardHelpDialog.js';
-import Screen from './Screen.js';
+import Screen, { UnknownScreen } from './Screen.js';
 import PickRequired from '../../phet-core/js/types/PickRequired.js';
 import TReadOnlyProperty from '../../axon/js/TReadOnlyProperty.js';
 
@@ -37,7 +37,7 @@
    * @param backgroundColorProperty
    * @param [providedOptions]
    */
-  public constructor( screenProperty: Property<Screen>,
+  public constructor( screenProperty: Property<UnknownScreen>,
                       backgroundColorProperty: TReadOnlyProperty<Color>,
                       providedOptions: KeyboardHelpButtonOptions ) {
 
Index: main/joist/js/NavigationBar.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/main/joist/js/NavigationBar.ts b/main/joist/js/NavigationBar.ts
--- a/main/joist/js/NavigationBar.ts	(revision 967a7adee65fefc039bffad000ae7726fc668b95)
+++ b/main/joist/js/NavigationBar.ts	(date 1661801541138)
@@ -38,10 +38,10 @@
 import joistStrings from './joistStrings.js';
 import NavigationBarScreenButton from './NavigationBarScreenButton.js';
 import PhetButton from './PhetButton.js';
-import Sim from './Sim.js';
+import Sim, { UnknownSim } from './Sim.js';
 import ReadOnlyProperty from '../../axon/js/ReadOnlyProperty.js';
 import Bounds2 from '../../dot/js/Bounds2.js';
-import Screen from './Screen.js';
+import Screen, { UnknownScreen } from './Screen.js';
 import BooleanProperty from '../../axon/js/BooleanProperty.js';
 
 // constants
@@ -71,7 +71,7 @@
   private readonly localeNode!: Node;
   private readonly homeButton: HomeButton | null = null; // mutated if multiscreen sim
 
-  public constructor( sim: Sim, tandem: Tandem ) {
+  public constructor( sim: UnknownSim, tandem: Tandem ) {
 
     super();
 
@@ -245,7 +245,7 @@
       } )!.width );
       const maxScreenButtonHeight = _.maxBy( screenButtons, button => button.height )!.height;
 
-      const screenButtonMap = new Map<Screen<IntentionalAny>, Node>();
+      const screenButtonMap = new Map<UnknownScreen, Node>();
       screenButtons.forEach( screenButton => {
         screenButtonMap.set( screenButton.screen, new AlignBox( screenButton, {
           excludeInvisibleChildrenFromBounds: true,
Index: main/joist/js/selectScreens.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/main/joist/js/selectScreens.ts b/main/joist/js/selectScreens.ts
--- a/main/joist/js/selectScreens.ts	(revision 967a7adee65fefc039bffad000ae7726fc668b95)
+++ b/main/joist/js/selectScreens.ts	(date 1661802865755)
@@ -2,12 +2,14 @@
 import joist from './joist.js';
 import HomeScreen from './HomeScreen.js';
 import Screen from './Screen.js';
+import ScreenView from './ScreenView.js';
+import TModel from './TModel.js';
 
-export type ScreenReturnType = {
+export type ScreenReturnType<M1 extends TModel, V1 extends ScreenView, M2 extends TModel, V2 extends ScreenView, M3 extends TModel, V3 extends ScreenView, M4 extends TModel, V4 extends ScreenView, M5 extends TModel, V5 extends ScreenView, M6 extends TModel, V6 extends ScreenView> = {
   homeScreen: HomeScreen | null;
-  initialScreen: Screen;
-  selectedSimScreens: Screen[];
-  screens: Screen[];
+  initialScreen: ( Screen<M1, V1> | Screen<M2, V2> | Screen<M3, V3> | Screen<M4, V4> | Screen<M5, V5> | Screen<M6, V6> | HomeScreen );
+  selectedSimScreens: ( Screen<M1, V1> | Screen<M2, V2> | Screen<M3, V3> | Screen<M4, V4> | Screen<M5, V5> | Screen<M6, V6> )[];
+  screens: ( Screen<M1, V1> | Screen<M2, V2> | Screen<M3, V3> | Screen<M4, V4> | Screen<M5, V5> | Screen<M6, V6> | HomeScreen )[];
   allScreensCreated: boolean;
 };
 
@@ -29,19 +31,21 @@
  * @param initialScreenQueryParameterProvided
  * @param screensQueryParameter - from phet.chipper.queryParameters.screens
  * @param screensQueryParameterProvided
+ * @param setupScreens
  * @param createHomeScreen
  * @returns - duck-typed for tests
  * @throws Error if incompatible data is provided
  */
-export default function selectScreens( allSimScreens: Screen[],
-                                       homeScreenQueryParameter: boolean,
-                                       homeScreenQueryParameterProvided: boolean,
-                                       initialScreenIndex: number,
-                                       initialScreenQueryParameterProvided: boolean,
-                                       screensQueryParameter: number[],
-                                       screensQueryParameterProvided: boolean,
-                                       setupScreens: ( screens: Screen[] ) => void,
-                                       createHomeScreen: ( screens: Screen[] ) => HomeScreen ): ScreenReturnType {
+export default function selectScreens<M1 extends TModel, V1 extends ScreenView, M2 extends TModel, V2 extends ScreenView, M3 extends TModel, V3 extends ScreenView, M4 extends TModel, V4 extends ScreenView, M5 extends TModel, V5 extends ScreenView, M6 extends TModel, V6 extends ScreenView>(
+  allSimScreens: ( Screen<M1, V1> | Screen<M2, V2> | Screen<M3, V3> | Screen<M4, V4> | Screen<M5, V5> | Screen<M6, V6> )[],
+  homeScreenQueryParameter: boolean,
+  homeScreenQueryParameterProvided: boolean,
+  initialScreenIndex: number,
+  initialScreenQueryParameterProvided: boolean,
+  screensQueryParameter: number[],
+  screensQueryParameterProvided: boolean,
+  setupScreens: ( screens: ( Screen<M1, V1> | Screen<M2, V2> | Screen<M3, V3> | Screen<M4, V4> | Screen<M5, V5> | Screen<M6, V6> )[] ) => void,
+  createHomeScreen: ( screens: ( Screen<M1, V1> | Screen<M2, V2> | Screen<M3, V3> | Screen<M4, V4> | Screen<M5, V5> | Screen<M6, V6> )[] ) => HomeScreen ): ScreenReturnType<M1, V1, M2, V2, M3, V3, M4, V4, M5, V5, M6, V6> {
 
   if ( allSimScreens.length === 1 && homeScreenQueryParameterProvided && homeScreenQueryParameter ) {
     const errorMessage = 'cannot specify homeScreen=true for single-screen sims';
@@ -54,7 +58,7 @@
   }
 
   // the ordered list of sim screens for this runtime
-  let selectedSimScreens = [];
+  let selectedSimScreens: ( Screen<M1, V1> | Screen<M2, V2> | Screen<M3, V3> | Screen<M4, V4> | Screen<M5, V5> | Screen<M6, V6> )[] = [];
 
   // If a subset of screens was specified with the `screens` query parameter, add them to selectedSimScreens. Otherwise,
   // use all of the available sim screens as the default. Note that if the value of `screens` did not pass validation
@@ -119,7 +123,7 @@
     assert && assert( false, errorMessage );
   }
 
-  const screens = selectedSimScreens.slice();
+  const screens: ( Screen<M1, V1> | Screen<M2, V2> | Screen<M3, V3> | Screen<M4, V4> | Screen<M5, V5> | Screen<M6, V6> | HomeScreen )[] = selectedSimScreens.slice();
 
   let homeScreen = null;
 
Index: main/joist/js/HomeScreenModel.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/main/joist/js/HomeScreenModel.ts b/main/joist/js/HomeScreenModel.ts
--- a/main/joist/js/HomeScreenModel.ts	(revision 967a7adee65fefc039bffad000ae7726fc668b95)
+++ b/main/joist/js/HomeScreenModel.ts	(date 1661803080972)
@@ -13,13 +13,15 @@
 import IntentionalAny from '../../phet-core/js/types/IntentionalAny.js';
 import Tandem from '../../tandem/js/Tandem.js';
 import joist from './joist.js';
-import Screen from './Screen.js';
+import Screen, { UnknownScreen, UnknownSimScreen } from './Screen.js';
+import TModel from './TModel.js';
+import ScreenView from './ScreenView.js';
 
-class HomeScreenModel {
-  public simScreens: Screen<IntentionalAny>[]; // screens in the simulations that are not the HomeScreen
-  public screenProperty: Property<Screen<IntentionalAny>>;
-  public selectedScreenProperty: Property<Screen<IntentionalAny>>;
-  public readonly activeSimScreensProperty: ReadOnlyProperty<Screen[]>;
+class HomeScreenModel<M1 extends TModel, V1 extends ScreenView, M2 extends TModel, V2 extends ScreenView, M3 extends TModel, V3 extends ScreenView, M4 extends TModel, V4 extends ScreenView, M5 extends TModel, V5 extends ScreenView, M6 extends TModel, V6 extends ScreenView> implements TModel {
+  public simScreens: UnknownScreen[]; // screens in the simulations that are not the HomeScreen
+  public screenProperty: Property<UnknownScreen>;
+  public selectedScreenProperty: Property<Screen<M1, V1> | Screen<M2, V2> | Screen<M3, V3> | Screen<M4, V4> | Screen<M5, V5> | Screen<M6, V6>>;
+  public readonly activeSimScreensProperty: ReadOnlyProperty<UnknownScreen[]>;
 
   /**
    * @param screenProperty - the screen that is displayed to the user in the main area above the
@@ -27,7 +29,7 @@
    * @param simScreens
    * @param tandem
    */
-  public constructor( screenProperty: Property<Screen<IntentionalAny, IntentionalAny>>, simScreens: Screen<IntentionalAny, IntentionalAny>[], activeSimScreensProperty: ReadOnlyProperty<Screen[]>, tandem: Tandem ) {
+  public constructor( screenProperty: Property<Screen<IntentionalAny, IntentionalAny>>, simScreens: Screen<IntentionalAny, IntentionalAny>[], activeSimScreensProperty: ReadOnlyProperty<UnknownScreen[]>, tandem: Tandem ) {
 
     this.simScreens = simScreens;
     this.screenProperty = screenProperty;
Index: main/joist/js/thirdPartySupport/LegendsOfLearningSupport.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/main/joist/js/thirdPartySupport/LegendsOfLearningSupport.ts b/main/joist/js/thirdPartySupport/LegendsOfLearningSupport.ts
--- a/main/joist/js/thirdPartySupport/LegendsOfLearningSupport.ts	(revision 967a7adee65fefc039bffad000ae7726fc668b95)
+++ b/main/joist/js/thirdPartySupport/LegendsOfLearningSupport.ts	(date 1661801691755)
@@ -10,13 +10,13 @@
  */
 
 import joist from '../joist.js';
-import Sim from '../Sim.js';
+import { UnknownSim } from '../Sim.js';
 
 class LegendsOfLearningSupport {
 
-  private readonly sim: Sim;
+  private readonly sim: UnknownSim;
 
-  public constructor( sim: Sim ) {
+  public constructor( sim: UnknownSim ) {
     this.sim = sim;
 
     // Respond to pause/resume commands from the Legends of Learning platform
Index: main/geometric-optics-basics/js/geometric-optics-basics-main.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/main/geometric-optics-basics/js/geometric-optics-basics-main.ts b/main/geometric-optics-basics/js/geometric-optics-basics-main.ts
--- a/main/geometric-optics-basics/js/geometric-optics-basics-main.ts	(revision 9665271fa7c73ca1c1a5249f5578470237af7871)
+++ b/main/geometric-optics-basics/js/geometric-optics-basics-main.ts	(date 1661800421187)
@@ -25,7 +25,7 @@
     GOPreferences.add2FPointsCheckboxProperty.value = true;
   }
 
-  const sim = new GOSim( geometricOpticsBasicsStrings[ 'geometric-optics-basics' ].title, {
+  const sim = GOSim.create( geometricOpticsBasicsStrings[ 'geometric-optics-basics' ].title, {
     isBasicsVersion: true,
     phetioDesigned: true
   } );
Index: main/joist/js/ScreenSelectionSoundGenerator.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/main/joist/js/ScreenSelectionSoundGenerator.ts b/main/joist/js/ScreenSelectionSoundGenerator.ts
--- a/main/joist/js/ScreenSelectionSoundGenerator.ts	(revision 967a7adee65fefc039bffad000ae7726fc668b95)
+++ b/main/joist/js/ScreenSelectionSoundGenerator.ts	(date 1661801560684)
@@ -11,12 +11,12 @@
 import SoundClip, { SoundClipOptions } from '../../tambo/js/sound-generators/SoundClip.js';
 import screenSelection_mp3 from '../sounds/screenSelection_mp3.js';
 import joist from './joist.js';
-import Screen from './Screen.js';
+import Screen, { UnknownScreen } from './Screen.js';
 import HomeScreen from './HomeScreen.js';
 
 class ScreenSelectionSoundGenerator extends SoundClip {
 
-  public constructor( screenProperty: ReadOnlyProperty<Screen>, homeScreen: HomeScreen | null, options?: SoundClipOptions ) {
+  public constructor( screenProperty: ReadOnlyProperty<UnknownScreen>, homeScreen: HomeScreen | null, options?: SoundClipOptions ) {
 
     super( screenSelection_mp3, options );
 
Index: main/wave-interference/js/slits/SlitsScreen.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/main/wave-interference/js/slits/SlitsScreen.ts b/main/wave-interference/js/slits/SlitsScreen.ts
--- a/main/wave-interference/js/slits/SlitsScreen.ts	(revision 19d020ea56b6652cfa0aefb9c2b0990fada24fbf)
+++ b/main/wave-interference/js/slits/SlitsScreen.ts	(date 1661802199410)
@@ -19,7 +19,7 @@
 
 const screenSlitsString = waveInterferenceStrings.screen.slits;
 
-class SlitsScreen extends Screen {
+class SlitsScreen extends Screen<SlitsModel, SlitsScreenView> {
 
   /**
    * @param alignGroup - for aligning the control panels on the right side of the lattice

@pixelzoom
Copy link
Contributor

Discussed with @samreid. There's definitiely something odd going on here, or maybe something about TypeScript that we don't understand. But this seems like lower prioirty at the moment, so I recommend keepin the ts-ignore in Screen.ts, with a link to this issue. I.e.:

// @ts-ignore
type CreateView<out M extends TModel, V> = ( model: M ) => V;

@samreid please feel free to add additional notes that might be helpful in the future, then feel free to label as deferred.

@pixelzoom pixelzoom removed their assignment Aug 29, 2022
@samreid
Copy link
Member

samreid commented Aug 30, 2022

Summarizing key points from my discussion with @pixelzoom:

  • The problematic ts-ignore is in ScreenView:
// @ts-ignore
type CreateView<out M extends TModel, V> = ( model: M ) => V;
  • Removing this ts-ignore yields: this type error:
/Users/samreid/apache-document-root/main/joist/js/Screen.ts(73,17): error TS2636: Type 'CreateView<sub-M, V>' is not assignable to type 'CreateView<super-M, V>' as implied by variance annotation.
  Types of parameters 'model' and 'model' are incompatible.
    Type 'super-M' is not assignable to type 'sub-M'.

This is a buggy type annotation because M is in the in location, and out is not correct. Removing the variance annotation like so: type CreateView<M extends TModel, V> = ( model: M ) => V; gives 73 errors in sim code like:

/Users/samreid/apache-document-root/main/geometric-optics/js/GOSim.ts(58,7): error TS2322: Type 'LensScreen' is not assignable to type 'Screen<TModel, ScreenView>'.
  Types of property 'createView' are incompatible.
    Type 'CreateView<LensModel, LensScreenView>' is not assignable to type 'CreateView<TModel, ScreenView>'.
      Type 'TModel' is missing the following properties from type 'LensModel': lens, reset, opticalObjectChoiceProperty, raysTypeProperty, and 12 more.

The problem is that Sim only knows that it has a TModel. So type checking createView(LensModel) fails because it doesn't know for sure that it is a LensModel. That is why the proposal in #783 (comment) is to add more type information to Sim so it knows what the sim model and view types are. However, that patch leads to complex, verbose and redundant code like:

+export default function selectScreens<M1 extends TModel, V1 extends ScreenView, M2 extends TModel, V2 extends ScreenView, M3 extends TModel, V3 extends ScreenView, M4 extends TModel, V4 extends ScreenView, M5 extends TModel, V5 extends ScreenView, M6 extends TModel, V6 extends ScreenView>(
+  allSimScreens: ( Screen<M1, V1> | Screen<M2, V2> | Screen<M3, V3> | Screen<M4, V4> | Screen<M5, V5> | Screen<M6, V6> )[],
+  homeScreenQueryParameter: boolean,

And still has many type errors left to correct. Those changes are nice because it distinguishes between sim screens and the homescreen, but there is just too much overhead to justify it.

The other problem @pixelzoom and I observed is that Screen<TModel,ScreenView> could not accept a value of HomeScreen<HomeScreenModel,HomeScreenView> even though HomeScreenModel implements TModel and HomeScreenView extends ScreenView. This is likely because HomeScreenModel appears in the contravariant position and the view appears in the covariant position.

Our conclusion is that we think if we had better TypeScript expertise, there would be a more elegant solution, perhaps using a more array-like strategy for the Sim's individual Screen types. But for now, we would like to shut off this error with an isolated ts-ignore or any or something. The existing commit does that (in an awkward way).

samreid added a commit that referenced this issue Aug 30, 2022
@samreid
Copy link
Member

samreid commented Aug 30, 2022

I changed from the incorrect variance annotation to one IntentionalAny in the commit.

@samreid samreid removed their assignment Aug 30, 2022
@samreid samreid changed the title Create an IModel interface Create a TModel type alias Aug 30, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants