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

PhET-iO: How to support dynamic range for ISLCObject.positionProperty #172

Closed
zepumph opened this issue Aug 3, 2019 · 11 comments
Closed

Comments

@zepumph
Copy link
Member

zepumph commented Aug 3, 2019

This is a problem in Studio because the enabledRange is constantly changing, but that isn't reflected in the positionProperty.range metadata in studio.

@zepumph
Copy link
Member Author

zepumph commented Aug 3, 2019

From #172

@samreid
Copy link
Member

samreid commented Aug 3, 2019

Is this primarily a problem because the studio controls let you go out of the enabled range? Should the positionProperty have a rangeProperty?

@zepumph
Copy link
Member Author

zepumph commented Aug 6, 2019

Is this primarily a problem because the studio controls let you go out of the enabled range?

Yes studio sets the controls for the initial state, but even by the time startup is complete the range has changed. So the studio control is out of sync with the range. Do you think that we could fix this by adding support for NumberProperty.range to be {Range|Property.<Range>}? Even still I'm unsure how we would get the studio control to behave properly.

@zepumph
Copy link
Member Author

zepumph commented Aug 20, 2019

At the very least we should set phetioStudioControl=false to the object's positionProperty so that it isn't so easy to break the sim in Studio.

@zepumph
Copy link
Member Author

zepumph commented Aug 21, 2019

Discussed with @chrisklus @samreid and @zepumph. We decided that we would add support for Property for range in NumberProperty.

We also decided that in this sim, enabledRangeProperty is strange as a sibling to positionProperty, and instead it should be positionProperty.rangeProperty as a phetioID.

@zepumph
Copy link
Member Author

zepumph commented Aug 22, 2019

I was able to implement everything discussed above, and it seemed like things were going very well. I then proceeded to run into tremendous trouble about validating the dynamic range within the ISLC model. Basically I ran into more than 3 places (and counting) where the dynamic range was being updated such that it excluded the current position, or the position was trying to be set outside of the current range. My best guess is that this has to do with the order in which positionProperty and its range is being set. Included in this patch are a few fixes (or at least I think) to this problem. All in all this has turned out to be a bit of a headache. I was able to do the numberProperty stuff in about an hour, and have been trying to sort out ISLC specific buggy logic for 90+ minutes.

Index: phet-io-wrappers/studio/StudioInstanceCreator.js
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- phet-io-wrappers/studio/StudioInstanceCreator.js	(revision 6d48cabead79a1e232f757c0203cc31ae5a229b3)
+++ phet-io-wrappers/studio/StudioInstanceCreator.js	(date 1566435721578)
@@ -537,35 +537,62 @@
       const input = document.createElement( 'input' );
       input.type = 'range';
       input.name = 'points';
+
+      // These defaults are overwritten if more range data is specified.
       let min = 0;
       let max = 1;
 
-      if ( stateObject.range ) {
-        min = stateObject.range.min;
-        const unitsString = stateObject.units || '';
-        div.appendChild( createSpan( 'min: ' + min + ' ' + unitsString, { className: 'displayBlock' } ) );
+      // create these eagerly so they can be referenced in `updateRange`
+      const minSpan = createSpan( '', { className: 'displayBlock' } );
+      const maxSpan = createSpan( '', { className: 'displayBlock' } );
+      const unitsString = stateObject.units || '';
 
-        max = stateObject.range.max;
-        div.appendChild( createSpan( 'max: ' + max + ' ' + unitsString, { className: 'displayBlock' } ) );
-      }
+      // Update the range pieces of the div, to support dynamic range, see https://github.com/phetsims/gravity-force-lab/issues/172
+      const updateRangeAndStep = () => {
+
+        // set the range text
+        minSpan.innerHTML = 'min: ' + min + ' ' + unitsString;
+        maxSpan.innerHTML = 'max: ' + max + ' ' + unitsString;
 
-      // Edge cases for infinity
-      if ( typeof min === 'string' && min.indexOf( 'INFINITY' ) >= 0 ) {
-        min = -100;
-      }
-      if ( typeof max === 'string' && max.indexOf( 'INFINITY' ) >= 0 ) {
-        max = 100;
-      }
-      input.min = min + '';
-      input.max = max + '';
+        // Edge cases for infinity
+        if ( typeof min === 'string' && min.indexOf( 'INFINITY' ) >= 0 ) {
+          min = -100;
+        }
+        if ( typeof max === 'string' && max.indexOf( 'INFINITY' ) >= 0 ) {
+          max = 100;
+        }
+
+        input.min = min + '';
+        input.max = max + '';
 
-      // integral sliders should only try to set integer values
-      if ( stateObject.numberType === 'Integer' ) {
-        input.step = 1;
-      }
-      else {
-        input.step = ( max - min ) / 100 + '';
+        // integral sliders should only try to set integer values. Recompute step each time range is updated
+        if ( stateObject.numberType === 'Integer' ) {
+          input.step = 1;
+        }
+        else {
+          input.step = ( max - min ) / 100 + '';
+        }
+      };
+
+      if ( stateObject.range ) {
+        min = stateObject.range.min;
+        max = stateObject.range.max;
+
+        div.appendChild( minSpan );
+        div.appendChild( maxSpan );
+
+        // The presence of `rangePhetioID means it is a dynamic range Property, so link to it
+        if ( stateObject.rangePhetioID ) {
+          phetioClient.invoke( stateObject.rangePhetioID, 'link', [ newRange => {
+            min = newRange.min;
+            max = newRange.max;
+            updateRangeAndStep();
+          } ] );
+        }
       }
+
+      // support for static ranges
+      updateRangeAndStep();
 
       let onlyHasOneValue = false;
       if ( max === min ) {
Index: inverse-square-law-common/js/view/ISLCObjectNode.js
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- inverse-square-law-common/js/view/ISLCObjectNode.js	(revision b0234acfc4b4852e41b38f752ab07208b0e41b8f)
+++ inverse-square-law-common/js/view/ISLCObjectNode.js	(date 1566437801771)
@@ -209,10 +209,8 @@
         // drag position relative to the pointer pointer start position and convert to model coordinates
         let x = modelViewTransform.viewToModelX( this.globalToParentPoint( event.pointer.point ).x - clickOffset );
 
-        // absolute drag bounds based on model
-        // see method descriptions for details
-        const xMax = model.getObjectMaxPosition( object );
-        const xMin = model.getObjectMinPosition( object );
+        const xMax = object.positionProperty.range.value.max;
+        const xMin = object.positionProperty.range.value.min;
 
         // apply limitations and update position
         x = Math.max( Math.min( x, xMax ), xMin ); // limited value of x (by boundary) in model coordinates
@@ -282,7 +280,7 @@
     // a11y - initialize the accessible slider, which makes this Node act like an accessible range input
     this.initializeAccessibleSlider(
       object.positionProperty,
-      object.enabledRangeProperty,
+      object.positionProperty.range,
       new BooleanProperty( true ), // always enabled
       accessibleSliderOptions
     );
Index: inverse-square-law-common/js/view/describers/PositionDescriber.js
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- inverse-square-law-common/js/view/describers/PositionDescriber.js	(revision b0234acfc4b4852e41b38f752ab07208b0e41b8f)
+++ inverse-square-law-common/js/view/describers/PositionDescriber.js	(date 1566432287162)
@@ -393,7 +393,7 @@
      */
     objectAtTouchingMin( objectEnum ) {
       const object = this.getObjectFromEnum( objectEnum );
-      return object.positionProperty.get() === object.enabledRangeProperty.get().min;
+      return object.positionProperty.get() === object.positionProperty.range.min;
     }
 
     /**
@@ -405,7 +405,7 @@
      */
     objectAtTouchingMax( objectEnum ) {
       const object = this.getObjectFromEnum( objectEnum );
-      return object.positionProperty.get() === object.enabledRangeProperty.get().max;
+      return object.positionProperty.get() === object.positionProperty.range.max;
     }
 
     /**
Index: inverse-square-law-common/js/model/ISLCObject.js
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- inverse-square-law-common/js/model/ISLCObject.js	(revision b0234acfc4b4852e41b38f752ab07208b0e41b8f)
+++ inverse-square-law-common/js/model/ISLCObject.js	(date 1566437831284)
@@ -9,6 +9,7 @@
   'use strict';
 
   // modules
+  const BooleanProperty = require( 'AXON/BooleanProperty' );
   const DerivedProperty = require( 'AXON/DerivedProperty' );
   const DerivedPropertyIO = require( 'AXON/DerivedPropertyIO' );
   const Emitter = require( 'AXON/Emitter' );
@@ -44,11 +45,20 @@
       valueUnits: 'kilograms'
     }, options );
 
+    const enabledRange = new Range( options.leftObjectBoundary, options.rightObjectBoundary );
+
+    const positionPropertyTandem = tandem.createTandem( 'positionProperty' );
+
     // @public
     this.positionProperty = new NumberProperty( initialPosition, {
-      tandem: tandem.createTandem( 'positionProperty' ),
+      tandem: positionPropertyTandem,
       units: 'meters',
-      range: new Range( options.leftObjectBoundary, options.rightObjectBoundary ),
+      range: new Property( enabledRange, {
+        tandem: positionPropertyTandem.createTandem( 'rangeProperty' ),
+        phetioDocumentation: 'The range for position of this object based on the radius and location of both objects',
+        phetioType: PropertyIO( RangeIO ),
+        phetioReadOnly: true
+      } ),
       phetioDocumentation: 'The position of the object along the track, in meters.'
     } );
 
@@ -73,17 +83,6 @@
       }
     );
 
-    const enabledRange = new Range( options.leftObjectBoundary, options.rightObjectBoundary );
-
-    // @public {Property.<Range>}- set by ISLCModel when the force changes, the range for position of the objects
-    // based on their radius and location
-    this.enabledRangeProperty = new Property( enabledRange, {
-      tandem: tandem.createTandem( 'enabledRangeProperty' ),
-      phetioDocumentation: 'The range for position of this object based on the radius and location of both objects',
-      phetioType: PropertyIO( RangeIO ),
-      phetioReadOnly: true
-    } );
-
     // @public (read-only) - Emitter that fires whenever the position changes as a result of an object's value changing.
     // Emits with the objectEnum that caused the position change.
     this.positionChangedFromSecondarySourceEmitter = new Emitter( { parameters: [ { valueType: ISLCObjectEnum } ] } );
@@ -99,9 +98,9 @@
     // from a value change. See https://github.com/phetsims/gravity-force-lab-basics/issues/168
     this.valueChangedSinceLastStep = false;
 
-    // @public (read-only) - flag flipped when the constant radius boolean is toggled. This is used to check if a position change is
+    // @public (read-only) {Property.<boolean>} - flipped when the constant radius boolean is toggled. This is used to check if a position change is
     // caused from the constant radius toggling. See https://github.com/phetsims/gravity-force-lab-basics/issues/168
-    this.constantRadiusChangedSinceLastStep = false;
+    this.constantRadiusChangedSinceLastStepProperty = new BooleanProperty( false );
 
     // @public
     this.valueRange = valueRange;
@@ -120,7 +119,7 @@
 
     // Link these flags to their associatedProperties
     this.valueProperty.link( () => { this.valueChangedSinceLastStep = true; } );
-    constantRadiusProperty.link( () => { this.constantRadiusChangedSinceLastStep = true; } );
+    constantRadiusProperty.link( () => { this.constantRadiusChangedSinceLastStepProperty.value = true; } );
   }
 
   inverseSquareLawCommon.register( 'ISLCObject', ISLCObject );
@@ -133,7 +132,7 @@
      */
     onStepEnd: function() {
       this.valueChangedSinceLastStep = false;
-      this.constantRadiusChangedSinceLastStep = false;
+      this.constantRadiusChangedSinceLastStepProperty.value = false;
     },
 
     /**
Index: inverse-square-law-common/js/model/ISLCModel.js
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- inverse-square-law-common/js/model/ISLCModel.js	(revision b0234acfc4b4852e41b38f752ab07208b0e41b8f)
+++ inverse-square-law-common/js/model/ISLCModel.js	(date 1566438127061)
@@ -85,19 +85,14 @@
       units: 'newtons',
       phetioDocumentation: 'The force of one object on the other (in Newtons)'
     } );
+    window.model = this;
 
-    const updateRange = object => {
-      const maxPosition = this.getObjectMaxPosition( object );
-      const minPosition = this.getObjectMinPosition( object );
 
-      object.enabledRangeProperty.set( new Range( minPosition, maxPosition ) );
-    };
-
-    // a11y - necessary to reset the enabledRangeProperty to prevent object overlap, disposal not necessary
+    // a11y - necessary to reset the positionProperty.range to prevent object overlap, disposal not necessary
     // We need to update the available range for each object when the either's radius or position changes.
     Property.multilink( [ object1.positionProperty, object2.positionProperty ], () => {
-      updateRange( object1 );
-      updateRange( object2 );
+      this.updateRange( object1 );
+      this.updateRange( object2 );
     } );
 
     // when sim is reset, we only reset the position properties of each object to their initial values
@@ -105,19 +100,11 @@
     this.object1.radiusProperty.link( () => {
       this.object1.radiusLastChanged = true;
       this.object2.radiusLastChanged = false;
-
-      // update range if radius changed with "constant radius" setting (which didn't trigger other model updates)
-      updateRange( object1 );
-      updateRange( object2 );
     } );
 
     this.object2.radiusProperty.link( () => {
       this.object2.radiusLastChanged = true;
       this.object1.radiusLastChanged = false;
-
-      // update range if radius changed with "constant radius" setting (which didn't trigger other model updates)
-      updateRange( object2 );
-      updateRange( object1 );
     } );
 
     // wire up logic to update the state of the pushedObjectEnumProperty
@@ -160,6 +147,7 @@
 
   return inherit( Object, ISLCModel, {
 
+
     /**
      * Step function makes sure objects don't go out of bounds and don't overlap each other at each time step.
      *
@@ -245,11 +233,25 @@
           }
         }
       }
+      // update range if radius changed with "constant radius" setting (which didn't trigger other model updates)
+      if ( this.object1.constantRadiusChangedSinceLastStepProperty.value ||
+           this.object2.constantRadiusChangedSinceLastStepProperty.value ) {
+        this.updateRange( this.object1 );
+        this.updateRange( this.object2 );
+      }
 
       // broadcast a message that we have updated the model
       this.stepEmitter.emit();
     },
 
+    updateRange: function( object ) {
+      const maxPosition = this.getObjectMaxPosition( object );
+      const minPosition = this.getObjectMinPosition( object );
+
+      object.positionProperty.range.set( new Range( minPosition, maxPosition ) );
+
+    },
+
     /**
      * Helper function to for accessing and mapping force ranges in the inheriting sims' views
      * @returns {number} - positive number, representing the magnitude of the force vector
Index: axon/js/NumberProperty.js
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- axon/js/NumberProperty.js	(date 1566431411000)
+++ axon/js/NumberProperty.js	(date 1566436303575)
@@ -32,12 +32,14 @@
       options = _.extend( {
         numberType: 'FloatingPoint', // {string} see VALID_NUMBER_TYPES
 
-        // {Range|null} range
+        // {Range|Property.<Range>|null} range
         range: null
       }, options );
 
       assert && assert( _.includes( VALID_NUMBER_TYPES, options.numberType ), 'invalid numberType: ' + options.numberType );
-      assert && options.range && assert( options.range instanceof Range, 'options.range must be of type Range:' + options.range );
+      assert && options.range && assert( options.range instanceof Range ||
+                                         ( options.range instanceof Property && options.range.value instanceof Range ),
+        'options.range must be of type Range or Property.<Range>:' + options.range );
 
       // client cannot specify superclass options that are controlled by NumberProperty
       assert && assert( !options.valueType, 'NumberProperty sets valueType' );
@@ -54,7 +56,7 @@
       // @public (read-only) - used by PhET-iO in NumberPropertyIO as metadata passed to the wrapper.
       this.numberType = options.numberType;
 
-      // @public {Range|null} (read-only) - If defined, provides the range of possible values (inclusive)
+      // @public {Range|Property.<Range>|null} (read-only) - if defined, provides the range of possible values (inclusive)
       this.range = options.range;
 
       // @private {function|null} value validation that is specific to NumberProperty, null if assertions are disabled
@@ -64,7 +66,11 @@
         options.numberType === 'Integer' && validate( value, VALID_INTEGER );
 
         // validate for range
-        options.range && validate( value, { isValidValue: v => options.range.contains( v ) } );
+        if ( options.range ) {
+          const currentRange = options.range instanceof Property ? options.range.value : options.range;
+          assert && assert( currentRange instanceof Range, `unexpected Range: ${currentRange}` );
+          validate( value, { isValidValue: v => currentRange.contains( v ) } );
+        }
       } );
 
       // verify that validValues meet other NumberProperty-specific validation criteria
@@ -72,10 +78,27 @@
         options.validValues.forEach( this.assertNumberPropertyValidateValue );
       }
 
+      // @private - keep track for disposal
+      this.rangeChangeListener = () => {
+        this.assertNumberPropertyValidateValue( this.value );
+      };
+      if ( options.range && options.range instanceof Property ) {
+        options.range.link( this.rangeChangeListener );
+      }
+
       // validate initial value
       this.assertNumberPropertyValidateValue && this.assertNumberPropertyValidateValue( value );
     }
 
+    /**
+     * @public
+     * @override
+     */
+    dispose() {
+      this.range && this.range instanceof Property && this.range.unlink( this.rangeChangeListener );
+      super.dispose();
+    }
+
     /**
      * Performs value validation that is specific to NumberProperty.
      * Then sets the value and notifies listeners.
Index: axon/js/NumberPropertyIO.js
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- axon/js/NumberPropertyIO.js	(date 1566431411000)
+++ axon/js/NumberPropertyIO.js	(date 1566433001346)
@@ -13,10 +13,14 @@
   const axon = require( 'AXON/axon' );
   const NumberIO = require( 'TANDEM/types/NumberIO' );
   const phetioInherit = require( 'TANDEM/phetioInherit' );
+  const Property = require( 'AXON/Property' );
   const PropertyIO = require( 'AXON/PropertyIO' );
   const RangeIO = require( 'DOT/RangeIO' );
   const validate = require( 'AXON/validate' );
 
+  // ifphetio
+  const phetioEngine = require( 'ifphetio!PHET_IO/phetioEngine' );
+
   // constants
   const PropertyIOImpl = PropertyIO( NumberIO );
 
@@ -60,7 +64,11 @@
       }
 
       if ( numberProperty.range ) {
-        parentStateObject.range = RangeIO.toStateObject( numberProperty.range );
+        const range = numberProperty.range instanceof Property ? numberProperty.range.value : numberProperty.range;
+        parentStateObject.range = RangeIO.toStateObject( range );
+      }
+      if ( numberProperty.range instanceof Property && numberProperty.isPhetioInstrumented() ) {
+        parentStateObject.rangePhetioID = numberProperty.range.tandem.phetioID;
       }
       return parentStateObject;
     },
@@ -76,7 +84,9 @@
       fromParentStateObject.numberType = stateObject.numberType;
 
       // Create Range instance if defined, otherwise preserve value of null or undefined.
-      fromParentStateObject.range = stateObject.range ? RangeIO.fromStateObject( stateObject.range ) : stateObject.range;
+      fromParentStateObject.range = stateObject.rangePhetioID ? phetioEngine.getPhetioObject( stateObject.rangePhetioID ) :
+                                    stateObject.range ? RangeIO.fromStateObject( stateObject.range ) :
+                                    stateObject.range;
       return fromParentStateObject;
     },
 
Index: gravity-force-lab-basics/js/phet-io/gravity-force-lab-basics-phet-io-types.js
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- gravity-force-lab-basics/js/phet-io/gravity-force-lab-basics-phet-io-types.js	(revision a261a5d5e5f660759c4f250ca68ed251300a09ae)
+++ gravity-force-lab-basics/js/phet-io/gravity-force-lab-basics-phet-io-types.js	(date 1566435833475)
@@ -1065,7 +1065,7 @@
       ],
       "methods": {
         "addEventListener": {
-          "documentation": "Adds a listener to the PhET-iO dataStream, which can be used to respond to events or for data analysis. Unlike Client.launchSim( {onEvent} ) which is called recursively for each child event, this is only called with top-level events.",
+          "documentation": "Adds a listener to the PhET-iO dataStream, which can be used to respond to events or for data analysis. Unlike Client.launchSim( {onEvent} ) which is called recursively for each parsed child event, this is only called with stringified top-level events.",
           "parameterTypes": [
             "FunctionIO.(ObjectIO)=>VoidIO"
           ],
Index: gravity-force-lab-basics/js/phet-io/gravity-force-lab-basics-phet-io-elements-baseline.js
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- gravity-force-lab-basics/js/phet-io/gravity-force-lab-basics-phet-io-elements-baseline.js	(revision a261a5d5e5f660759c4f250ca68ed251300a09ae)
+++ gravity-force-lab-basics/js/phet-io/gravity-force-lab-basics-phet-io-elements-baseline.js	(date 1566435833432)
@@ -913,29 +913,29 @@
       "phetioStudioControl": true,
       "phetioTypeName": "DerivedPropertyIO.<NumberIO>"
     },
-    "gravityForceLabBasics.gravityForceLabBasicsScreen.model.mass1.enabledRangeProperty": {
-      "phetioDocumentation": "The range for position of this object based on the radius and location of both objects",
+    "gravityForceLabBasics.gravityForceLabBasicsScreen.model.mass1.positionProperty": {
+      "phetioDocumentation": "The position of the object along the track, in meters.",
       "phetioDynamicElement": false,
       "phetioEventType": "MODEL",
       "phetioFeatured": false,
       "phetioHighFrequency": false,
       "phetioPlayback": false,
-      "phetioReadOnly": true,
+      "phetioReadOnly": false,
       "phetioState": true,
       "phetioStudioControl": true,
-      "phetioTypeName": "PropertyIO.<RangeIO>"
+      "phetioTypeName": "NumberPropertyIO"
     },
-    "gravityForceLabBasics.gravityForceLabBasicsScreen.model.mass1.positionProperty": {
-      "phetioDocumentation": "The position of the object along the track, in meters.",
+    "gravityForceLabBasics.gravityForceLabBasicsScreen.model.mass1.positionProperty.rangeProperty": {
+      "phetioDocumentation": "The range for position of this object based on the radius and location of both objects",
       "phetioDynamicElement": false,
       "phetioEventType": "MODEL",
       "phetioFeatured": false,
       "phetioHighFrequency": false,
       "phetioPlayback": false,
-      "phetioReadOnly": false,
+      "phetioReadOnly": true,
       "phetioState": true,
       "phetioStudioControl": true,
-      "phetioTypeName": "NumberPropertyIO"
+      "phetioTypeName": "PropertyIO.<RangeIO>"
     },
     "gravityForceLabBasics.gravityForceLabBasicsScreen.model.mass1.radiusProperty": {
       "phetioDocumentation": "The radius of the object",
@@ -961,29 +961,29 @@
       "phetioStudioControl": true,
       "phetioTypeName": "NumberPropertyIO"
     },
-    "gravityForceLabBasics.gravityForceLabBasicsScreen.model.mass2.enabledRangeProperty": {
-      "phetioDocumentation": "The range for position of this object based on the radius and location of both objects",
+    "gravityForceLabBasics.gravityForceLabBasicsScreen.model.mass2.positionProperty": {
+      "phetioDocumentation": "The position of the object along the track, in meters.",
       "phetioDynamicElement": false,
       "phetioEventType": "MODEL",
       "phetioFeatured": false,
       "phetioHighFrequency": false,
       "phetioPlayback": false,
-      "phetioReadOnly": true,
+      "phetioReadOnly": false,
       "phetioState": true,
       "phetioStudioControl": true,
-      "phetioTypeName": "PropertyIO.<RangeIO>"
+      "phetioTypeName": "NumberPropertyIO"
     },
-    "gravityForceLabBasics.gravityForceLabBasicsScreen.model.mass2.positionProperty": {
-      "phetioDocumentation": "The position of the object along the track, in meters.",
+    "gravityForceLabBasics.gravityForceLabBasicsScreen.model.mass2.positionProperty.rangeProperty": {
+      "phetioDocumentation": "The range for position of this object based on the radius and location of both objects",
       "phetioDynamicElement": false,
       "phetioEventType": "MODEL",
       "phetioFeatured": false,
       "phetioHighFrequency": false,
       "phetioPlayback": false,
-      "phetioReadOnly": false,
+      "phetioReadOnly": true,
       "phetioState": true,
       "phetioStudioControl": true,
-      "phetioTypeName": "NumberPropertyIO"
+      "phetioTypeName": "PropertyIO.<RangeIO>"
     },
     "gravityForceLabBasics.gravityForceLabBasicsScreen.model.mass2.radiusProperty": {
       "phetioDocumentation": "The radius of the object",
Index: coulombs-law/js/phet-io/coulombs-law-phet-io-elements-baseline.js
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- coulombs-law/js/phet-io/coulombs-law-phet-io-elements-baseline.js	(revision 84a77e9531078ee6110679557d6544b8bf3163b2)
+++ coulombs-law/js/phet-io/coulombs-law-phet-io-elements-baseline.js	(date 1566435824541)
@@ -37,29 +37,29 @@
       "phetioStudioControl": true,
       "phetioTypeName": "PropertyIO.<BooleanIO>"
     },
-    "coulombsLaw.atomicScreen.coulombsLawAtomicScreen.model.charge1.enabledRangeProperty": {
-      "phetioDocumentation": "The range for position of this object based on the radius and location of both objects",
+    "coulombsLaw.atomicScreen.coulombsLawAtomicScreen.model.charge1.positionProperty": {
+      "phetioDocumentation": "The position of the object along the track, in meters.",
       "phetioDynamicElement": false,
       "phetioEventType": "MODEL",
       "phetioFeatured": false,
       "phetioHighFrequency": false,
       "phetioPlayback": false,
-      "phetioReadOnly": true,
+      "phetioReadOnly": false,
       "phetioState": true,
       "phetioStudioControl": true,
-      "phetioTypeName": "PropertyIO.<RangeIO>"
+      "phetioTypeName": "NumberPropertyIO"
     },
-    "coulombsLaw.atomicScreen.coulombsLawAtomicScreen.model.charge1.positionProperty": {
-      "phetioDocumentation": "The position of the object along the track, in meters.",
+    "coulombsLaw.atomicScreen.coulombsLawAtomicScreen.model.charge1.positionProperty.rangeProperty": {
+      "phetioDocumentation": "The range for position of this object based on the radius and location of both objects",
       "phetioDynamicElement": false,
       "phetioEventType": "MODEL",
       "phetioFeatured": false,
       "phetioHighFrequency": false,
       "phetioPlayback": false,
-      "phetioReadOnly": false,
+      "phetioReadOnly": true,
       "phetioState": true,
       "phetioStudioControl": true,
-      "phetioTypeName": "NumberPropertyIO"
+      "phetioTypeName": "PropertyIO.<RangeIO>"
     },
     "coulombsLaw.atomicScreen.coulombsLawAtomicScreen.model.charge1.radiusProperty": {
       "phetioDocumentation": "The radius of the object",
@@ -109,29 +109,29 @@
       "phetioStudioControl": true,
       "phetioTypeName": "PropertyIO.<BooleanIO>"
     },
-    "coulombsLaw.atomicScreen.coulombsLawAtomicScreen.model.charge2.enabledRangeProperty": {
-      "phetioDocumentation": "The range for position of this object based on the radius and location of both objects",
+    "coulombsLaw.atomicScreen.coulombsLawAtomicScreen.model.charge2.positionProperty": {
+      "phetioDocumentation": "The position of the object along the track, in meters.",
       "phetioDynamicElement": false,
       "phetioEventType": "MODEL",
       "phetioFeatured": false,
       "phetioHighFrequency": false,
       "phetioPlayback": false,
-      "phetioReadOnly": true,
+      "phetioReadOnly": false,
       "phetioState": true,
       "phetioStudioControl": true,
-      "phetioTypeName": "PropertyIO.<RangeIO>"
+      "phetioTypeName": "NumberPropertyIO"
     },
-    "coulombsLaw.atomicScreen.coulombsLawAtomicScreen.model.charge2.positionProperty": {
-      "phetioDocumentation": "The position of the object along the track, in meters.",
+    "coulombsLaw.atomicScreen.coulombsLawAtomicScreen.model.charge2.positionProperty.rangeProperty": {
+      "phetioDocumentation": "The range for position of this object based on the radius and location of both objects",
       "phetioDynamicElement": false,
       "phetioEventType": "MODEL",
       "phetioFeatured": false,
       "phetioHighFrequency": false,
       "phetioPlayback": false,
-      "phetioReadOnly": false,
+      "phetioReadOnly": true,
       "phetioState": true,
       "phetioStudioControl": true,
-      "phetioTypeName": "NumberPropertyIO"
+      "phetioTypeName": "PropertyIO.<RangeIO>"
     },
     "coulombsLaw.atomicScreen.coulombsLawAtomicScreen.model.charge2.radiusProperty": {
       "phetioDocumentation": "The radius of the object",
@@ -5053,29 +5053,29 @@
       "phetioStudioControl": true,
       "phetioTypeName": "PropertyIO.<BooleanIO>"
     },
-    "coulombsLaw.macroScreen.coulombsLawMacroScreen.model.charge1.enabledRangeProperty": {
-      "phetioDocumentation": "The range for position of this object based on the radius and location of both objects",
+    "coulombsLaw.macroScreen.coulombsLawMacroScreen.model.charge1.positionProperty": {
+      "phetioDocumentation": "The position of the object along the track, in meters.",
       "phetioDynamicElement": false,
       "phetioEventType": "MODEL",
       "phetioFeatured": false,
       "phetioHighFrequency": false,
       "phetioPlayback": false,
-      "phetioReadOnly": true,
+      "phetioReadOnly": false,
       "phetioState": true,
       "phetioStudioControl": true,
-      "phetioTypeName": "PropertyIO.<RangeIO>"
+      "phetioTypeName": "NumberPropertyIO"
     },
-    "coulombsLaw.macroScreen.coulombsLawMacroScreen.model.charge1.positionProperty": {
-      "phetioDocumentation": "The position of the object along the track, in meters.",
+    "coulombsLaw.macroScreen.coulombsLawMacroScreen.model.charge1.positionProperty.rangeProperty": {
+      "phetioDocumentation": "The range for position of this object based on the radius and location of both objects",
       "phetioDynamicElement": false,
       "phetioEventType": "MODEL",
       "phetioFeatured": false,
       "phetioHighFrequency": false,
       "phetioPlayback": false,
-      "phetioReadOnly": false,
+      "phetioReadOnly": true,
       "phetioState": true,
       "phetioStudioControl": true,
-      "phetioTypeName": "NumberPropertyIO"
+      "phetioTypeName": "PropertyIO.<RangeIO>"
     },
     "coulombsLaw.macroScreen.coulombsLawMacroScreen.model.charge1.radiusProperty": {
       "phetioDocumentation": "The radius of the object",
@@ -5125,29 +5125,29 @@
       "phetioStudioControl": true,
       "phetioTypeName": "PropertyIO.<BooleanIO>"
     },
-    "coulombsLaw.macroScreen.coulombsLawMacroScreen.model.charge2.enabledRangeProperty": {
-      "phetioDocumentation": "The range for position of this object based on the radius and location of both objects",
+    "coulombsLaw.macroScreen.coulombsLawMacroScreen.model.charge2.positionProperty": {
+      "phetioDocumentation": "The position of the object along the track, in meters.",
       "phetioDynamicElement": false,
       "phetioEventType": "MODEL",
       "phetioFeatured": false,
       "phetioHighFrequency": false,
       "phetioPlayback": false,
-      "phetioReadOnly": true,
+      "phetioReadOnly": false,
       "phetioState": true,
       "phetioStudioControl": true,
-      "phetioTypeName": "PropertyIO.<RangeIO>"
+      "phetioTypeName": "NumberPropertyIO"
     },
-    "coulombsLaw.macroScreen.coulombsLawMacroScreen.model.charge2.positionProperty": {
-      "phetioDocumentation": "The position of the object along the track, in meters.",
+    "coulombsLaw.macroScreen.coulombsLawMacroScreen.model.charge2.positionProperty.rangeProperty": {
+      "phetioDocumentation": "The range for position of this object based on the radius and location of both objects",
       "phetioDynamicElement": false,
       "phetioEventType": "MODEL",
       "phetioFeatured": false,
       "phetioHighFrequency": false,
       "phetioPlayback": false,
-      "phetioReadOnly": false,
+      "phetioReadOnly": true,
       "phetioState": true,
       "phetioStudioControl": true,
-      "phetioTypeName": "NumberPropertyIO"
+      "phetioTypeName": "PropertyIO.<RangeIO>"
     },
     "coulombsLaw.macroScreen.coulombsLawMacroScreen.model.charge2.radiusProperty": {
       "phetioDocumentation": "The radius of the object",
Index: coulombs-law/js/phet-io/coulombs-law-phet-io-types.js
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- coulombs-law/js/phet-io/coulombs-law-phet-io-types.js	(revision 84a77e9531078ee6110679557d6544b8bf3163b2)
+++ coulombs-law/js/phet-io/coulombs-law-phet-io-types.js	(date 1566435824670)
@@ -1275,7 +1275,7 @@
       ],
       "methods": {
         "addEventListener": {
-          "documentation": "Adds a listener to the PhET-iO dataStream, which can be used to respond to events or for data analysis. Unlike Client.launchSim( {onEvent} ) which is called recursively for each child event, this is only called with top-level events.",
+          "documentation": "Adds a listener to the PhET-iO dataStream, which can be used to respond to events or for data analysis. Unlike Client.launchSim( {onEvent} ) which is called recursively for each parsed child event, this is only called with stringified top-level events.",
           "parameterTypes": [
             "FunctionIO.(ObjectIO)=>VoidIO"
           ],
Index: gravity-force-lab/js/phet-io/gravity-force-lab-phet-io-elements-baseline.js
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- gravity-force-lab/js/phet-io/gravity-force-lab-phet-io-elements-baseline.js	(revision e959191e9bc26910900dfe4ccab00b2a18f9e0fc)
+++ gravity-force-lab/js/phet-io/gravity-force-lab-phet-io-elements-baseline.js	(date 1566435816378)
@@ -901,29 +901,29 @@
       "phetioStudioControl": true,
       "phetioTypeName": "DerivedPropertyIO.<NumberIO>"
     },
-    "gravityForceLab.gravityForceLabScreen.model.mass1.enabledRangeProperty": {
-      "phetioDocumentation": "The range for position of this object based on the radius and location of both objects",
+    "gravityForceLab.gravityForceLabScreen.model.mass1.positionProperty": {
+      "phetioDocumentation": "The position of the object along the track, in meters.",
       "phetioDynamicElement": false,
       "phetioEventType": "MODEL",
       "phetioFeatured": false,
       "phetioHighFrequency": false,
       "phetioPlayback": false,
-      "phetioReadOnly": true,
+      "phetioReadOnly": false,
       "phetioState": true,
       "phetioStudioControl": true,
-      "phetioTypeName": "PropertyIO.<RangeIO>"
+      "phetioTypeName": "NumberPropertyIO"
     },
-    "gravityForceLab.gravityForceLabScreen.model.mass1.positionProperty": {
-      "phetioDocumentation": "The position of the object along the track, in meters.",
+    "gravityForceLab.gravityForceLabScreen.model.mass1.positionProperty.rangeProperty": {
+      "phetioDocumentation": "The range for position of this object based on the radius and location of both objects",
       "phetioDynamicElement": false,
       "phetioEventType": "MODEL",
       "phetioFeatured": false,
       "phetioHighFrequency": false,
       "phetioPlayback": false,
-      "phetioReadOnly": false,
+      "phetioReadOnly": true,
       "phetioState": true,
       "phetioStudioControl": true,
-      "phetioTypeName": "NumberPropertyIO"
+      "phetioTypeName": "PropertyIO.<RangeIO>"
     },
     "gravityForceLab.gravityForceLabScreen.model.mass1.radiusProperty": {
       "phetioDocumentation": "The radius of the object",
@@ -949,29 +949,29 @@
       "phetioStudioControl": true,
       "phetioTypeName": "NumberPropertyIO"
     },
-    "gravityForceLab.gravityForceLabScreen.model.mass2.enabledRangeProperty": {
-      "phetioDocumentation": "The range for position of this object based on the radius and location of both objects",
+    "gravityForceLab.gravityForceLabScreen.model.mass2.positionProperty": {
+      "phetioDocumentation": "The position of the object along the track, in meters.",
       "phetioDynamicElement": false,
       "phetioEventType": "MODEL",
       "phetioFeatured": false,
       "phetioHighFrequency": false,
       "phetioPlayback": false,
-      "phetioReadOnly": true,
+      "phetioReadOnly": false,
       "phetioState": true,
       "phetioStudioControl": true,
-      "phetioTypeName": "PropertyIO.<RangeIO>"
+      "phetioTypeName": "NumberPropertyIO"
     },
-    "gravityForceLab.gravityForceLabScreen.model.mass2.positionProperty": {
-      "phetioDocumentation": "The position of the object along the track, in meters.",
+    "gravityForceLab.gravityForceLabScreen.model.mass2.positionProperty.rangeProperty": {
+      "phetioDocumentation": "The range for position of this object based on the radius and location of both objects",
       "phetioDynamicElement": false,
       "phetioEventType": "MODEL",
       "phetioFeatured": false,
       "phetioHighFrequency": false,
       "phetioPlayback": false,
-      "phetioReadOnly": false,
+      "phetioReadOnly": true,
       "phetioState": true,
       "phetioStudioControl": true,
-      "phetioTypeName": "NumberPropertyIO"
+      "phetioTypeName": "PropertyIO.<RangeIO>"
     },
     "gravityForceLab.gravityForceLabScreen.model.mass2.radiusProperty": {
       "phetioDocumentation": "The radius of the object",
Index: gravity-force-lab/js/gravity-force-lab/view/GravityForceLabAlertManager.js
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- gravity-force-lab/js/gravity-force-lab/view/GravityForceLabAlertManager.js	(revision e959191e9bc26910900dfe4ccab00b2a18f9e0fc)
+++ gravity-force-lab/js/gravity-force-lab/view/GravityForceLabAlertManager.js	(date 1566436787623)
@@ -94,7 +94,7 @@
       const secondaryPositionChangedListener = objectEnum => {
 
         // handle the case where the position changed from the constant radius being toggled
-        if ( model.object1.constantRadiusChangedSinceLastStep || model.object2.constantRadiusChangedSinceLastStep ) {
+        if ( model.object1.constantRadiusChangedSinceLastStepProperty.value || model.object2.constantRadiusChangedSinceLastStepProperty.value ) {
           this.alertConstantSizeChangedPosition();
         }
         else { // value specific assumption
Index: gravity-force-lab/js/phet-io/gravity-force-lab-phet-io-types.js
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- gravity-force-lab/js/phet-io/gravity-force-lab-phet-io-types.js	(revision e959191e9bc26910900dfe4ccab00b2a18f9e0fc)
+++ gravity-force-lab/js/phet-io/gravity-force-lab-phet-io-types.js	(date 1566435816420)
@@ -1175,7 +1175,7 @@
       ],
       "methods": {
         "addEventListener": {
-          "documentation": "Adds a listener to the PhET-iO dataStream, which can be used to respond to events or for data analysis. Unlike Client.launchSim( {onEvent} ) which is called recursively for each child event, this is only called with top-level events.",
+          "documentation": "Adds a listener to the PhET-iO dataStream, which can be used to respond to events or for data analysis. Unlike Client.launchSim( {onEvent} ) which is called recursively for each parsed child event, this is only called with stringified top-level events.",
           "parameterTypes": [
             "FunctionIO.(ObjectIO)=>VoidIO"
           ],

zepumph added a commit to phetsims/inverse-square-law-common that referenced this issue Aug 23, 2019
zepumph added a commit to phetsims/gravity-force-lab-basics that referenced this issue Aug 23, 2019
@zepumph
Copy link
Member Author

zepumph commented Sep 3, 2019

Again I tried to fix this, and continued to run into many problems within validating the range and the position as they change. I continued to hit new assertions as I tried to fix prior ones, and I don't feel like I know the model well enough to trust any of the hacky changes I'm making. It would potentially be nice to pair with @jessegreenberg on this, because I think he understands the model operations a bit better.

Index: phet-io-wrappers/studio/StudioInstanceCreator.js
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- phet-io-wrappers/studio/StudioInstanceCreator.js	(revision d492adc4fa577fac9bd57f413f320cf6a4c34894)
+++ phet-io-wrappers/studio/StudioInstanceCreator.js	(date 1567524788702)
@@ -537,35 +537,62 @@
       const input = document.createElement( 'input' );
       input.type = 'range';
       input.name = 'points';
+
+      // These defaults are overwritten if more range data is specified.
       let min = 0;
       let max = 1;
 
-      if ( stateObject.range ) {
-        min = stateObject.range.min;
-        const unitsString = stateObject.units || '';
-        div.appendChild( createSpan( 'min: ' + min + ' ' + unitsString, { className: 'displayBlock' } ) );
+      // create these eagerly so they can be referenced in `updateRange`
+      const minSpan = createSpan( '', { className: 'displayBlock' } );
+      const maxSpan = createSpan( '', { className:   'displayBlock' } );
+      const unitsString = stateObject.units || '';
 
-        max = stateObject.range.max;
-        div.appendChild( createSpan( 'max: ' + max + ' ' + unitsString, { className: 'displayBlock' } ) );
-      }
+      // Update the range pieces of the div, to support dynamic range, see https://github.com/phetsims/gravity-force-lab/issues/172
+      const updateRangeAndStep = () => {
+
+        // set the range text
+        minSpan.innerHTML = 'min: ' + min + ' ' + unitsString;
+        maxSpan.innerHTML = 'max: ' + max + ' ' + unitsString;
 
-      // Edge cases for infinity
-      if ( typeof min === 'string' && min.indexOf( 'INFINITY' ) >= 0 ) {
-        min = -100;
-      }
-      if ( typeof max === 'string' && max.indexOf( 'INFINITY' ) >= 0 ) {
-        max = 100;
-      }
-      input.min = min + '';
-      input.max = max + '';
+        // Edge cases for infinity
+        if ( typeof min === 'string' && min.indexOf( 'INFINITY' ) >= 0 ) {
+          min = -100;
+        }
+        if ( typeof max === 'string' && max.indexOf( 'INFINITY' ) >= 0 ) {
+          max = 100;
+        }
+
+        input.min = min + '';
+        input.max = max + '';
 
-      // integral sliders should only try to set integer values
-      if ( stateObject.numberType === 'Integer' ) {
-        input.step = 1;
-      }
-      else {
-        input.step = ( max - min ) / 100 + '';
+        // integral sliders should only try to set integer values. Recompute step each time range is updated
+        if ( stateObject.numberType === 'Integer' ) {
+          input.step = 1;
+        }
+        else {
+          input.step = ( max - min ) / 100 + '';
+        }
+      };
+
+      if ( stateObject.range ) {
+        min = stateObject.range.min;
+        max = stateObject.range.max;
+
+        div.appendChild( minSpan );
+        div.appendChild( maxSpan );
+
+        // The presence of `rangePhetioID means it is a dynamic range Property, so link to it
+        if ( stateObject.rangePhetioID ) {
+          phetioClient.invoke( stateObject.rangePhetioID, 'link', [ newRange => {
+            min = newRange.min;
+            max = newRange.max;
+            updateRangeAndStep();
+          } ] );
+        }
       }
+
+      // support for static ranges
+      updateRangeAndStep();
 
       let onlyHasOneValue = false;
       if ( max === min ) {
Index: inverse-square-law-common/js/view/ISLCObjectNode.js
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- inverse-square-law-common/js/view/ISLCObjectNode.js	(date 1566592619000)
+++ inverse-square-law-common/js/view/ISLCObjectNode.js	(date 1566603465314)
@@ -210,10 +210,8 @@
         // drag position relative to the pointer pointer start position and convert to model coordinates
         let x = modelViewTransform.viewToModelX( this.globalToParentPoint( event.pointer.point ).x - clickOffset );
 
-        // absolute drag bounds based on model
-        // see method descriptions for details
-        const xMax = model.getObjectMaxPosition( object );
-        const xMin = model.getObjectMinPosition( object );
+        const xMax = object.positionProperty.range.value.max;
+        const xMin = object.positionProperty.range.value.min;
 
         // apply limitations and update position
         x = Math.max( Math.min( x, xMax ), xMin ); // limited value of x (by boundary) in model coordinates
@@ -283,7 +281,7 @@
     // a11y - initialize the accessible slider, which makes this Node act like an accessible range input
     this.initializeAccessibleSlider(
       object.positionProperty,
-      object.enabledRangeProperty,
+      object.positionProperty.range,
       new BooleanProperty( true ), // always enabled
       accessibleSliderOptions
     );
Index: inverse-square-law-common/js/view/describers/PositionDescriber.js
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- inverse-square-law-common/js/view/describers/PositionDescriber.js	(date 1566592619000)
+++ inverse-square-law-common/js/view/describers/PositionDescriber.js	(date 1566603465309)
@@ -393,7 +393,7 @@
      */
     objectAtTouchingMin( objectEnum ) {
       const object = this.getObjectFromEnum( objectEnum );
-      return object.positionProperty.get() === object.enabledRangeProperty.get().min;
+      return object.positionProperty.get() === object.positionProperty.range.min;
     }
 
     /**
@@ -405,7 +405,7 @@
      */
     objectAtTouchingMax( objectEnum ) {
       const object = this.getObjectFromEnum( objectEnum );
-      return object.positionProperty.get() === object.enabledRangeProperty.get().max;
+      return object.positionProperty.get() === object.positionProperty.range.max;
     }
 
     /**
Index: inverse-square-law-common/js/model/ISLCObject.js
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- inverse-square-law-common/js/model/ISLCObject.js	(date 1566592619000)
+++ inverse-square-law-common/js/model/ISLCObject.js	(date 1566603465303)
@@ -9,6 +9,7 @@
   'use strict';
 
   // modules
+  const BooleanProperty = require( 'AXON/BooleanProperty' );
   const DerivedProperty = require( 'AXON/DerivedProperty' );
   const DerivedPropertyIO = require( 'AXON/DerivedPropertyIO' );
   const Emitter = require( 'AXON/Emitter' );
@@ -44,11 +45,20 @@
       valueUnits: 'kilograms'
     }, options );
 
+    const enabledRange = new Range( options.leftObjectBoundary, options.rightObjectBoundary );
+
+    const positionPropertyTandem = tandem.createTandem( 'positionProperty' );
+
     // @public
     this.positionProperty = new NumberProperty( initialPosition, {
-      tandem: tandem.createTandem( 'positionProperty' ),
+      tandem: positionPropertyTandem,
       units: 'meters',
-      range: new Range( options.leftObjectBoundary, options.rightObjectBoundary ),
+      range: new Property( enabledRange, {
+        tandem: positionPropertyTandem.createTandem( 'rangeProperty' ),
+        phetioDocumentation: 'The range for position of this object based on the radius and location of both objects',
+        phetioType: PropertyIO( RangeIO ),
+        phetioReadOnly: true
+      } ),
       phetioDocumentation: 'The position of the object along the track, in meters.',
       phetioStudioControl: false // temporary workaround until we support dynamic ranges in https://github.com/phetsims/gravity-force-lab/issues/172
     } );
@@ -74,17 +84,6 @@
       }
     );
 
-    const enabledRange = new Range( options.leftObjectBoundary, options.rightObjectBoundary );
-
-    // @public {Property.<Range>}- set by ISLCModel when the force changes, the range for position of the objects
-    // based on their radius and location
-    this.enabledRangeProperty = new Property( enabledRange, {
-      tandem: tandem.createTandem( 'enabledRangeProperty' ),
-      phetioDocumentation: 'The range for position of this object based on the radius and location of both objects',
-      phetioType: PropertyIO( RangeIO ),
-      phetioReadOnly: true
-    } );
-
     // @public (read-only) - Emitter that fires whenever the position changes as a result of an object's value changing.
     // Emits with the objectEnum that caused the position change.
     this.positionChangedFromSecondarySourceEmitter = new Emitter( { parameters: [ { valueType: ISLCObjectEnum } ] } );
@@ -100,9 +99,9 @@
     // from a value change. See https://github.com/phetsims/gravity-force-lab-basics/issues/168
     this.valueChangedSinceLastStep = false;
 
-    // @public (read-only) - flag flipped when the constant radius boolean is toggled. This is used to check if a position change is
+    // @public (read-only) {Property.<boolean>} - flipped when the constant radius boolean is toggled. This is used to check if a position change is
     // caused from the constant radius toggling. See https://github.com/phetsims/gravity-force-lab-basics/issues/168
-    this.constantRadiusChangedSinceLastStep = false;
+    this.constantRadiusChangedSinceLastStepProperty = new BooleanProperty( false );
 
     // @public
     this.valueRange = valueRange;
@@ -121,7 +120,7 @@
 
     // Link these flags to their associatedProperties
     this.valueProperty.link( () => { this.valueChangedSinceLastStep = true; } );
-    constantRadiusProperty.link( () => { this.constantRadiusChangedSinceLastStep = true; } );
+    constantRadiusProperty.link( () => { this.constantRadiusChangedSinceLastStepProperty.value = true; } );
   }
 
   inverseSquareLawCommon.register( 'ISLCObject', ISLCObject );
@@ -134,7 +133,7 @@
      */
     onStepEnd: function() {
       this.valueChangedSinceLastStep = false;
-      this.constantRadiusChangedSinceLastStep = false;
+      this.constantRadiusChangedSinceLastStepProperty.value = false;
     },
 
     /**
Index: inverse-square-law-common/js/model/ISLCModel.js
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- inverse-square-law-common/js/model/ISLCModel.js	(date 1566592619000)
+++ inverse-square-law-common/js/model/ISLCModel.js	(date 1567525898173)
@@ -87,6 +87,7 @@
       units: 'newtons',
       phetioDocumentation: 'The force of one object on the other (in Newtons)'
     } );
+    window.model = this;
 
     // @private {Property.<number>} - The distance between the two objects. Added for PhET-iO.
     this.separationProperty = new DerivedProperty( [
@@ -99,18 +100,12 @@
       phetioDocumentation: 'The distance between the two objects\' centers'
     } );
 
-    const updateRange = object => {
-      const maxPosition = this.getObjectMaxPosition( object );
-      const minPosition = this.getObjectMinPosition( object );
 
-      object.enabledRangeProperty.set( new Range( minPosition, maxPosition ) );
-    };
-
-    // a11y - necessary to reset the enabledRangeProperty to prevent object overlap, disposal not necessary
+    // a11y - necessary to reset the positionProperty.range to prevent object overlap, disposal not necessary
     // We need to update the available range for each object when the either's radius or position changes.
-    Property.multilink( [ object1.positionProperty, object2.positionProperty ], () => {
-      updateRange( object1 );
-      updateRange( object2 );
+    Property.multilink( [ object1.radiusProperty, object2.radiusProperty ], () => {
+      this.updateRange( object1 );
+      this.updateRange( object2 );
     } );
 
     // when sim is reset, we only reset the position properties of each object to their initial values
@@ -118,19 +113,11 @@
     this.object1.radiusProperty.link( () => {
       this.object1.radiusLastChanged = true;
       this.object2.radiusLastChanged = false;
-
-      // update range if radius changed with "constant radius" setting (which didn't trigger other model updates)
-      updateRange( object1 );
-      updateRange( object2 );
     } );
 
     this.object2.radiusProperty.link( () => {
       this.object2.radiusLastChanged = true;
       this.object1.radiusLastChanged = false;
-
-      // update range if radius changed with "constant radius" setting (which didn't trigger other model updates)
-      updateRange( object2 );
-      updateRange( object1 );
     } );
 
     // wire up logic to update the state of the pushedObjectEnumProperty
@@ -173,6 +160,7 @@
 
   return inherit( Object, ISLCModel, {
 
+
     /**
      * Step function makes sure objects don't go out of bounds and don't overlap each other at each time step.
      *
@@ -205,6 +193,7 @@
       locationObject1 = this.snapToGrid( locationObject1 );
       locationObject2 = this.snapToGrid( locationObject2 );
 
+      // TODO: try to see what happens if we update the range before we update the position each place here.
       if ( this.object1.isDragging ) {
         this.object1.positionProperty.set( locationObject1 );
       }
@@ -259,10 +248,20 @@
         }
       }
 
+
       // broadcast a message that we have updated the model
       this.stepEmitter.emit();
     },
 
+    updateRange: function( object ) {
+      const maxPosition = this.getObjectMaxPosition( object );
+      const minPosition = this.getObjectMinPosition( object );
+
+      // Matbe setting this first would fix a bug where the new range doesn't validate the old position?
+      object.positionProperty.value = Math.max( minPosition, Math.min( maxPosition, object.positionProperty.value ) );
+      object.positionProperty.range.set( new Range( minPosition, maxPosition ) );
+    },
+
     /**
      * Helper function to for accessing and mapping force ranges in the inheriting sims' views
      * @returns {number} - positive number, representing the magnitude of the force vector
Index: axon/js/NumberProperty.js
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- axon/js/NumberProperty.js	(revision cc407fd9db9d7c16835530041f328c1d34edfa83)
+++ axon/js/NumberProperty.js	(date 1567525787075)
@@ -32,12 +32,14 @@
       options = _.extend( {
         numberType: 'FloatingPoint', // {string} see VALID_NUMBER_TYPES
 
-        // {Range|null} range
+        // {Range|Property.<Range>|null} range
         range: null
       }, options );
 
       assert && assert( _.includes( VALID_NUMBER_TYPES, options.numberType ), 'invalid numberType: ' + options.numberType );
-      assert && options.range && assert( options.range instanceof Range, 'options.range must be of type Range:' + options.range );
+      assert && options.range && assert( options.range instanceof Range ||
+                                         ( options.range instanceof Property && options.range.value instanceof Range ),
+        'options.range must be of type Range or Property.<Range>:' + options.range );
 
       // client cannot specify superclass options that are controlled by NumberProperty
       assert && assert( !options.valueType, 'NumberProperty sets valueType' );
@@ -54,7 +56,7 @@
       // @public (read-only) - used by PhET-iO in NumberPropertyIO as metadata passed to the wrapper.
       this.numberType = options.numberType;
 
-      // @public {Range|null} (read-only) - If defined, provides the range of possible values (inclusive)
+      // @public {Range|Property.<Range>|null} (read-only) - if defined, provides the range of possible values (inclusive)
       this.range = options.range;
 
       // @private {function|null} value validation that is specific to NumberProperty, null if assertions are disabled
@@ -64,7 +66,11 @@
         options.numberType === 'Integer' && validate( value, VALID_INTEGER );
 
         // validate for range
-        options.range && validate( value, { isValidValue: v => options.range.contains( v ) } );
+        if ( options.range ) {
+          const currentRange = options.range instanceof Property ? options.range.value : options.range;
+          assert && assert( currentRange instanceof Range, `unexpected Range: ${currentRange}` );
+          validate( value, { isValidValue: v => currentRange.contains( v ) } );
+        }
       } );
 
       // verify that validValues meet other NumberProperty-specific validation criteria
@@ -72,10 +78,35 @@
         options.validValues.forEach( this.assertNumberPropertyValidateValue );
       }
 
+      // @private - keep track for disposal
+      this.rangeChangeListener = () => {
+        this.assertNumberPropertyValidateValue( this.value );
+      };
+      if ( options.range && options.range instanceof Property ) {
+        options.range.link( this.rangeChangeListener );
+      }
+
       // validate initial value
       this.assertNumberPropertyValidateValue && this.assertNumberPropertyValidateValue( value );
     }
 
+    /**
+     * @public
+     */
+    reset() {
+      this.range && this.range instanceof Property && this.range.reset();
+      super.reset();
+    }
+
+    /**
+     * @public
+     * @override
+     */
+    dispose() {
+      this.range && this.range instanceof Property && this.range.unlink( this.rangeChangeListener );
+      super.dispose();
+    }
+
     /**
      * Performs value validation that is specific to NumberProperty.
      * Then sets the value and notifies listeners.
Index: axon/js/NumberPropertyIO.js
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- axon/js/NumberPropertyIO.js	(revision cc407fd9db9d7c16835530041f328c1d34edfa83)
+++ axon/js/NumberPropertyIO.js	(date 1566603465141)
@@ -13,10 +13,14 @@
   const axon = require( 'AXON/axon' );
   const NumberIO = require( 'TANDEM/types/NumberIO' );
   const phetioInherit = require( 'TANDEM/phetioInherit' );
+  const Property = require( 'AXON/Property' );
   const PropertyIO = require( 'AXON/PropertyIO' );
   const RangeIO = require( 'DOT/RangeIO' );
   const validate = require( 'AXON/validate' );
 
+  // ifphetio
+  const phetioEngine = require( 'ifphetio!PHET_IO/phetioEngine' );
+
   // constants
   const PropertyIOImpl = PropertyIO( NumberIO );
 
@@ -60,7 +64,11 @@
       }
 
       if ( numberProperty.range ) {
-        parentStateObject.range = RangeIO.toStateObject( numberProperty.range );
+        const range = numberProperty.range instanceof Property ? numberProperty.range.value : numberProperty.range;
+        parentStateObject.range = RangeIO.toStateObject( range );
+      }
+      if ( numberProperty.range instanceof Property && numberProperty.isPhetioInstrumented() ) {
+        parentStateObject.rangePhetioID = numberProperty.range.tandem.phetioID;
       }
       return parentStateObject;
     },
@@ -76,7 +84,9 @@
       fromParentStateObject.numberType = stateObject.numberType;
 
       // Create Range instance if defined, otherwise preserve value of null or undefined.
-      fromParentStateObject.range = stateObject.range ? RangeIO.fromStateObject( stateObject.range ) : stateObject.range;
+      fromParentStateObject.range = stateObject.rangePhetioID ? phetioEngine.getPhetioObject( stateObject.rangePhetioID ) :
+                                    stateObject.range ? RangeIO.fromStateObject( stateObject.range ) :
+                                    stateObject.range;
       return fromParentStateObject;
     },
 
Index: gravity-force-lab-basics/js/phet-io/gravity-force-lab-basics-phet-io-elements-baseline.js
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- gravity-force-lab-basics/js/phet-io/gravity-force-lab-basics-phet-io-elements-baseline.js	(revision 8cc6b0a29b876bf3ddb2fe76a765fbd239a9324f)
+++ gravity-force-lab-basics/js/phet-io/gravity-force-lab-basics-phet-io-elements-baseline.js	(date 1566603465285)
@@ -901,8 +901,8 @@
       "phetioStudioControl": true,
       "phetioTypeName": "DerivedPropertyIO.<NumberIO>"
     },
-    "gravityForceLabBasics.gravityForceLabBasicsScreen.model.mass1.enabledRangeProperty": {
-      "phetioDocumentation": "The range for position of this object based on the radius and location of both objects",
+    "gravityForceLabBasics.gravityForceLabBasicsScreen.model.mass1.positionProperty": {
+      "phetioDocumentation": "The position of the object along the track, in meters.",
       "phetioDynamicElement": false,
       "phetioEventType": "MODEL",
       "phetioFeatured": false,
@@ -911,10 +911,10 @@
       "phetioReadOnly": true,
       "phetioState": true,
       "phetioStudioControl": true,
-      "phetioTypeName": "PropertyIO.<RangeIO>"
+      "phetioTypeName": "NumberPropertyIO"
     },
-    "gravityForceLabBasics.gravityForceLabBasicsScreen.model.mass1.positionProperty": {
-      "phetioDocumentation": "The position of the object along the track, in meters.",
+    "gravityForceLabBasics.gravityForceLabBasicsScreen.model.mass1.positionProperty.rangeProperty": {
+      "phetioDocumentation": "The range for position of this object based on the radius and location of both objects",
       "phetioDynamicElement": false,
       "phetioEventType": "MODEL",
       "phetioFeatured": false,
@@ -932,7 +932,7 @@
       "phetioFeatured": false,
       "phetioHighFrequency": false,
       "phetioPlayback": false,
-      "phetioReadOnly": true,
+      "phetioReadOnly": false,
       "phetioState": true,
       "phetioStudioControl": true,
       "phetioTypeName": "DerivedPropertyIO.<NumberIO>"
@@ -947,10 +947,10 @@
       "phetioReadOnly": false,
       "phetioState": true,
       "phetioStudioControl": true,
-      "phetioTypeName": "NumberPropertyIO"
+      "phetioTypeName": "PropertyIO.<RangeIO>"
     },
-    "gravityForceLabBasics.gravityForceLabBasicsScreen.model.mass2.enabledRangeProperty": {
-      "phetioDocumentation": "The range for position of this object based on the radius and location of both objects",
+    "gravityForceLabBasics.gravityForceLabBasicsScreen.model.mass2.positionProperty": {
+      "phetioDocumentation": "The position of the object along the track, in meters.",
       "phetioDynamicElement": false,
       "phetioEventType": "MODEL",
       "phetioFeatured": false,
@@ -959,10 +959,10 @@
       "phetioReadOnly": true,
       "phetioState": true,
       "phetioStudioControl": true,
-      "phetioTypeName": "PropertyIO.<RangeIO>"
+      "phetioTypeName": "NumberPropertyIO"
     },
-    "gravityForceLabBasics.gravityForceLabBasicsScreen.model.mass2.positionProperty": {
-      "phetioDocumentation": "The position of the object along the track, in meters.",
+    "gravityForceLabBasics.gravityForceLabBasicsScreen.model.mass2.positionProperty.rangeProperty": {
+      "phetioDocumentation": "The range for position of this object based on the radius and location of both objects",
       "phetioDynamicElement": false,
       "phetioEventType": "MODEL",
       "phetioFeatured": false,
@@ -980,7 +980,7 @@
       "phetioFeatured": false,
       "phetioHighFrequency": false,
       "phetioPlayback": false,
-      "phetioReadOnly": true,
+      "phetioReadOnly": false,
       "phetioState": true,
       "phetioStudioControl": true,
       "phetioTypeName": "DerivedPropertyIO.<NumberIO>"
@@ -995,7 +995,7 @@
       "phetioReadOnly": false,
       "phetioState": true,
       "phetioStudioControl": true,
-      "phetioTypeName": "NumberPropertyIO"
+      "phetioTypeName": "PropertyIO.<RangeIO>"
     },
     "gravityForceLabBasics.gravityForceLabBasicsScreen.model.resetInProgressProperty": {
       "phetioDocumentation": "",
Index: coulombs-law/js/phet-io/coulombs-law-phet-io-elements-baseline.js
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- coulombs-law/js/phet-io/coulombs-law-phet-io-elements-baseline.js	(revision a78800235bba543546ef1dd07d8b52c39c57a0aa)
+++ coulombs-law/js/phet-io/coulombs-law-phet-io-elements-baseline.js	(date 1566603465223)
@@ -37,20 +37,20 @@
       "phetioStudioControl": true,
       "phetioTypeName": "PropertyIO.<BooleanIO>"
     },
-    "coulombsLaw.atomicScreen.coulombsLawAtomicScreen.model.charge1.enabledRangeProperty": {
-      "phetioDocumentation": "The range for position of this object based on the radius and location of both objects",
+    "coulombsLaw.atomicScreen.coulombsLawAtomicScreen.model.charge1.positionProperty": {
+      "phetioDocumentation": "The position of the object along the track, in meters.",
       "phetioDynamicElement": false,
       "phetioEventType": "MODEL",
       "phetioFeatured": false,
       "phetioHighFrequency": false,
       "phetioPlayback": false,
-      "phetioReadOnly": true,
+      "phetioReadOnly": false,
       "phetioState": true,
       "phetioStudioControl": true,
-      "phetioTypeName": "PropertyIO.<RangeIO>"
+      "phetioTypeName": "NumberPropertyIO"
     },
-    "coulombsLaw.atomicScreen.coulombsLawAtomicScreen.model.charge1.positionProperty": {
-      "phetioDocumentation": "The position of the object along the track, in meters.",
+    "coulombsLaw.atomicScreen.coulombsLawAtomicScreen.model.charge1.positionProperty.rangeProperty": {
+      "phetioDocumentation": "The range for position of this object based on the radius and location of both objects",
       "phetioDynamicElement": false,
       "phetioEventType": "MODEL",
       "phetioFeatured": false,
@@ -59,7 +59,7 @@
       "phetioReadOnly": false,
       "phetioState": true,
       "phetioStudioControl": false,
-      "phetioTypeName": "NumberPropertyIO"
+      "phetioTypeName": "PropertyIO.<RangeIO>"
     },
     "coulombsLaw.atomicScreen.coulombsLawAtomicScreen.model.charge1.radiusProperty": {
       "phetioDocumentation": "The radius of the object",
@@ -80,7 +80,7 @@
       "phetioFeatured": false,
       "phetioHighFrequency": false,
       "phetioPlayback": false,
-      "phetioReadOnly": false,
+      "phetioReadOnly": true,
       "phetioState": true,
       "phetioStudioControl": true,
       "phetioTypeName": "NumberPropertyIO"
@@ -109,20 +109,20 @@
       "phetioStudioControl": true,
       "phetioTypeName": "PropertyIO.<BooleanIO>"
     },
-    "coulombsLaw.atomicScreen.coulombsLawAtomicScreen.model.charge2.enabledRangeProperty": {
-      "phetioDocumentation": "The range for position of this object based on the radius and location of both objects",
+    "coulombsLaw.atomicScreen.coulombsLawAtomicScreen.model.charge2.positionProperty": {
+      "phetioDocumentation": "The position of the object along the track, in meters.",
       "phetioDynamicElement": false,
       "phetioEventType": "MODEL",
       "phetioFeatured": false,
       "phetioHighFrequency": false,
       "phetioPlayback": false,
-      "phetioReadOnly": true,
+      "phetioReadOnly": false,
       "phetioState": true,
       "phetioStudioControl": true,
-      "phetioTypeName": "PropertyIO.<RangeIO>"
+      "phetioTypeName": "NumberPropertyIO"
     },
-    "coulombsLaw.atomicScreen.coulombsLawAtomicScreen.model.charge2.positionProperty": {
-      "phetioDocumentation": "The position of the object along the track, in meters.",
+    "coulombsLaw.atomicScreen.coulombsLawAtomicScreen.model.charge2.positionProperty.rangeProperty": {
+      "phetioDocumentation": "The range for position of this object based on the radius and location of both objects",
       "phetioDynamicElement": false,
       "phetioEventType": "MODEL",
       "phetioFeatured": false,
@@ -131,7 +131,7 @@
       "phetioReadOnly": false,
       "phetioState": true,
       "phetioStudioControl": false,
-      "phetioTypeName": "NumberPropertyIO"
+      "phetioTypeName": "PropertyIO.<RangeIO>"
     },
     "coulombsLaw.atomicScreen.coulombsLawAtomicScreen.model.charge2.radiusProperty": {
       "phetioDocumentation": "The radius of the object",
@@ -152,7 +152,7 @@
       "phetioFeatured": false,
       "phetioHighFrequency": false,
       "phetioPlayback": false,
-      "phetioReadOnly": false,
+      "phetioReadOnly": true,
       "phetioState": true,
       "phetioStudioControl": true,
       "phetioTypeName": "NumberPropertyIO"
@@ -5072,10 +5072,10 @@
       "phetioFeatured": false,
       "phetioHighFrequency": false,
       "phetioPlayback": false,
-      "phetioReadOnly": false,
+      "phetioReadOnly": true,
       "phetioState": true,
       "phetioStudioControl": true,
-      "phetioTypeName": "NumberPropertyIO"
+      "phetioTypeName": "PropertyIO.<RangeIO>"
     },
     "coulombsLaw.homeScreen.view.title.pickableProperty": {
       "phetioDocumentation": "Sets whether the node will be pickable (and hence interactive), see the NodeIO documentation for more details",
@@ -5132,7 +5132,7 @@
       "phetioFeatured": false,
       "phetioHighFrequency": false,
       "phetioPlayback": false,
-      "phetioReadOnly": true,
+      "phetioReadOnly": false,
       "phetioState": true,
       "phetioStudioControl": true,
       "phetioTypeName": "PropertyIO.<BooleanIO>"
@@ -5144,7 +5144,7 @@
       "phetioFeatured": false,
       "phetioHighFrequency": false,
       "phetioPlayback": false,
-      "phetioReadOnly": true,
+      "phetioReadOnly": false,
       "phetioState": true,
       "phetioStudioControl": true,
       "phetioTypeName": "DerivedPropertyIO.<ColorIO>"
@@ -5161,8 +5161,8 @@
       "phetioStudioControl": true,
       "phetioTypeName": "PropertyIO.<BooleanIO>"
     },
-    "coulombsLaw.macroScreen.coulombsLawMacroScreen.model.charge1.enabledRangeProperty": {
-      "phetioDocumentation": "The range for position of this object based on the radius and location of both objects",
+    "coulombsLaw.macroScreen.coulombsLawMacroScreen.model.charge1.positionProperty": {
+      "phetioDocumentation": "The position of the object along the track, in meters.",
       "phetioDynamicElement": false,
       "phetioEventType": "MODEL",
       "phetioFeatured": false,
@@ -5171,10 +5171,10 @@
       "phetioReadOnly": true,
       "phetioState": true,
       "phetioStudioControl": true,
-      "phetioTypeName": "PropertyIO.<RangeIO>"
+      "phetioTypeName": "NumberPropertyIO"
     },
-    "coulombsLaw.macroScreen.coulombsLawMacroScreen.model.charge1.positionProperty": {
-      "phetioDocumentation": "The position of the object along the track, in meters.",
+    "coulombsLaw.macroScreen.coulombsLawMacroScreen.model.charge1.positionProperty.rangeProperty": {
+      "phetioDocumentation": "The range for position of this object based on the radius and location of both objects",
       "phetioDynamicElement": false,
       "phetioEventType": "MODEL",
       "phetioFeatured": false,
@@ -5207,7 +5207,7 @@
       "phetioReadOnly": false,
       "phetioState": true,
       "phetioStudioControl": true,
-      "phetioTypeName": "NumberPropertyIO"
+      "phetioTypeName": "PropertyIO.<RangeIO>"
     },
     "coulombsLaw.macroScreen.coulombsLawMacroScreen.model.charge2.baseColorProperty": {
       "phetioDocumentation": "",
@@ -5233,8 +5233,8 @@
       "phetioStudioControl": true,
       "phetioTypeName": "PropertyIO.<BooleanIO>"
     },
-    "coulombsLaw.macroScreen.coulombsLawMacroScreen.model.charge2.enabledRangeProperty": {
-      "phetioDocumentation": "The range for position of this object based on the radius and location of both objects",
+    "coulombsLaw.macroScreen.coulombsLawMacroScreen.model.charge2.positionProperty": {
+      "phetioDocumentation": "The position of the object along the track, in meters.",
       "phetioDynamicElement": false,
       "phetioEventType": "MODEL",
       "phetioFeatured": false,
@@ -5243,10 +5243,10 @@
       "phetioReadOnly": true,
       "phetioState": true,
       "phetioStudioControl": true,
-      "phetioTypeName": "PropertyIO.<RangeIO>"
+      "phetioTypeName": "NumberPropertyIO"
     },
-    "coulombsLaw.macroScreen.coulombsLawMacroScreen.model.charge2.positionProperty": {
-      "phetioDocumentation": "The position of the object along the track, in meters.",
+    "coulombsLaw.macroScreen.coulombsLawMacroScreen.model.charge2.positionProperty.rangeProperty": {
+      "phetioDocumentation": "The range for position of this object based on the radius and location of both objects",
       "phetioDynamicElement": false,
       "phetioEventType": "MODEL",
       "phetioFeatured": false,
Index: gravity-force-lab/js/phet-io/gravity-force-lab-phet-io-elements-baseline.js
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- gravity-force-lab/js/phet-io/gravity-force-lab-phet-io-elements-baseline.js	(revision 7f3203a104af39298d30f01fadeff0dbffeaf7b9)
+++ gravity-force-lab/js/phet-io/gravity-force-lab-phet-io-elements-baseline.js	(date 1566603465260)
@@ -901,20 +901,20 @@
       "phetioStudioControl": true,
       "phetioTypeName": "DerivedPropertyIO.<NumberIO>"
     },
-    "gravityForceLab.gravityForceLabScreen.model.mass1.enabledRangeProperty": {
-      "phetioDocumentation": "The range for position of this object based on the radius and location of both objects",
+    "gravityForceLab.gravityForceLabScreen.model.mass1.positionProperty": {
+      "phetioDocumentation": "The position of the object along the track, in meters.",
       "phetioDynamicElement": false,
       "phetioEventType": "MODEL",
       "phetioFeatured": false,
       "phetioHighFrequency": false,
       "phetioPlayback": false,
-      "phetioReadOnly": true,
+      "phetioReadOnly": false,
       "phetioState": true,
       "phetioStudioControl": true,
-      "phetioTypeName": "PropertyIO.<RangeIO>"
+      "phetioTypeName": "NumberPropertyIO"
     },
-    "gravityForceLab.gravityForceLabScreen.model.mass1.positionProperty": {
-      "phetioDocumentation": "The position of the object along the track, in meters.",
+    "gravityForceLab.gravityForceLabScreen.model.mass1.positionProperty.rangeProperty": {
+      "phetioDocumentation": "The range for position of this object based on the radius and location of both objects",
       "phetioDynamicElement": false,
       "phetioEventType": "MODEL",
       "phetioFeatured": false,
@@ -923,7 +923,7 @@
       "phetioReadOnly": false,
       "phetioState": true,
       "phetioStudioControl": false,
-      "phetioTypeName": "NumberPropertyIO"
+      "phetioTypeName": "PropertyIO.<RangeIO>"
     },
     "gravityForceLab.gravityForceLabScreen.model.mass1.radiusProperty": {
       "phetioDocumentation": "The radius of the object",
@@ -944,25 +944,25 @@
       "phetioFeatured": false,
       "phetioHighFrequency": false,
       "phetioPlayback": false,
-      "phetioReadOnly": false,
+      "phetioReadOnly": true,
       "phetioState": true,
       "phetioStudioControl": true,
       "phetioTypeName": "NumberPropertyIO"
     },
-    "gravityForceLab.gravityForceLabScreen.model.mass2.enabledRangeProperty": {
-      "phetioDocumentation": "The range for position of this object based on the radius and location of both objects",
+    "gravityForceLab.gravityForceLabScreen.model.mass2.positionProperty": {
+      "phetioDocumentation": "The position of the object along the track, in meters.",
       "phetioDynamicElement": false,
       "phetioEventType": "MODEL",
       "phetioFeatured": false,
       "phetioHighFrequency": false,
       "phetioPlayback": false,
-      "phetioReadOnly": true,
+      "phetioReadOnly": false,
       "phetioState": true,
       "phetioStudioControl": true,
-      "phetioTypeName": "PropertyIO.<RangeIO>"
+      "phetioTypeName": "NumberPropertyIO"
     },
-    "gravityForceLab.gravityForceLabScreen.model.mass2.positionProperty": {
-      "phetioDocumentation": "The position of the object along the track, in meters.",
+    "gravityForceLab.gravityForceLabScreen.model.mass2.positionProperty.rangeProperty": {
+      "phetioDocumentation": "The range for position of this object based on the radius and location of both objects",
       "phetioDynamicElement": false,
       "phetioEventType": "MODEL",
       "phetioFeatured": false,
@@ -971,7 +971,7 @@
       "phetioReadOnly": false,
       "phetioState": true,
       "phetioStudioControl": false,
-      "phetioTypeName": "NumberPropertyIO"
+      "phetioTypeName": "PropertyIO.<RangeIO>"
     },
     "gravityForceLab.gravityForceLabScreen.model.mass2.radiusProperty": {
       "phetioDocumentation": "The radius of the object",
@@ -992,7 +992,7 @@
       "phetioFeatured": false,
       "phetioHighFrequency": false,
       "phetioPlayback": false,
-      "phetioReadOnly": false,
+      "phetioReadOnly": true,
       "phetioState": true,
       "phetioStudioControl": true,
       "phetioTypeName": "NumberPropertyIO"
Index: gravity-force-lab/js/gravity-force-lab/view/GravityForceLabAlertManager.js
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- gravity-force-lab/js/gravity-force-lab/view/GravityForceLabAlertManager.js	(revision 7f3203a104af39298d30f01fadeff0dbffeaf7b9)
+++ gravity-force-lab/js/gravity-force-lab/view/GravityForceLabAlertManager.js	(date 1566603465236)
@@ -94,7 +94,7 @@
       const secondaryPositionChangedListener = objectEnum => {
 
         // handle the case where the position changed from the constant radius being toggled
-        if ( model.object1.constantRadiusChangedSinceLastStep || model.object2.constantRadiusChangedSinceLastStep ) {
+        if ( model.object1.constantRadiusChangedSinceLastStepProperty.value || model.object2.constantRadiusChangedSinceLastStepProperty.value ) {
           this.alertConstantSizeChangedPosition();
         }
         else { // value specific assumption

@zepumph
Copy link
Member Author

zepumph commented Sep 3, 2019

@jessegreenberg and I worked on this, and we were able to think through the problem a bit more thoroughly.

It seemed like good things happened when we decided to update the range of the positionProperty whenever a positionProperty was set that would require changing. (i.e. update object2 range when object1 position changes, or update object2 range when object1 radius changed). This more manual update seemed to work a bit nicer. but we are still getting an assertion where the range is being updated to something that the current position doesn't validate in.

Index: phet-io-wrappers/studio/StudioInstanceCreator.js
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- phet-io-wrappers/studio/StudioInstanceCreator.js	(revision d492adc4fa577fac9bd57f413f320cf6a4c34894)
+++ phet-io-wrappers/studio/StudioInstanceCreator.js	(date 1567524788702)
@@ -537,35 +537,62 @@
       const input = document.createElement( 'input' );
       input.type = 'range';
       input.name = 'points';
+
+      // These defaults are overwritten if more range data is specified.
       let min = 0;
       let max = 1;
 
-      if ( stateObject.range ) {
-        min = stateObject.range.min;
-        const unitsString = stateObject.units || '';
-        div.appendChild( createSpan( 'min: ' + min + ' ' + unitsString, { className: 'displayBlock' } ) );
+      // create these eagerly so they can be referenced in `updateRange`
+      const minSpan = createSpan( '', { className: 'displayBlock' } );
+      const maxSpan = createSpan( '', { className:   'displayBlock' } );
+      const unitsString = stateObject.units || '';
 
-        max = stateObject.range.max;
-        div.appendChild( createSpan( 'max: ' + max + ' ' + unitsString, { className: 'displayBlock' } ) );
-      }
+      // Update the range pieces of the div, to support dynamic range, see https://github.com/phetsims/gravity-force-lab/issues/172
+      const updateRangeAndStep = () => {
+
+        // set the range text
+        minSpan.innerHTML = 'min: ' + min + ' ' + unitsString;
+        maxSpan.innerHTML = 'max: ' + max + ' ' + unitsString;
 
-      // Edge cases for infinity
-      if ( typeof min === 'string' && min.indexOf( 'INFINITY' ) >= 0 ) {
-        min = -100;
-      }
-      if ( typeof max === 'string' && max.indexOf( 'INFINITY' ) >= 0 ) {
-        max = 100;
-      }
-      input.min = min + '';
-      input.max = max + '';
+        // Edge cases for infinity
+        if ( typeof min === 'string' && min.indexOf( 'INFINITY' ) >= 0 ) {
+          min = -100;
+        }
+        if ( typeof max === 'string' && max.indexOf( 'INFINITY' ) >= 0 ) {
+          max = 100;
+        }
+
+        input.min = min + '';
+        input.max = max + '';
 
-      // integral sliders should only try to set integer values
-      if ( stateObject.numberType === 'Integer' ) {
-        input.step = 1;
-      }
-      else {
-        input.step = ( max - min ) / 100 + '';
+        // integral sliders should only try to set integer values. Recompute step each time range is updated
+        if ( stateObject.numberType === 'Integer' ) {
+          input.step = 1;
+        }
+        else {
+          input.step = ( max - min ) / 100 + '';
+        }
+      };
+
+      if ( stateObject.range ) {
+        min = stateObject.range.min;
+        max = stateObject.range.max;
+
+        div.appendChild( minSpan );
+        div.appendChild( maxSpan );
+
+        // The presence of `rangePhetioID means it is a dynamic range Property, so link to it
+        if ( stateObject.rangePhetioID ) {
+          phetioClient.invoke( stateObject.rangePhetioID, 'link', [ newRange => {
+            min = newRange.min;
+            max = newRange.max;
+            updateRangeAndStep();
+          } ] );
+        }
       }
+
+      // support for static ranges
+      updateRangeAndStep();
 
       let onlyHasOneValue = false;
       if ( max === min ) {
Index: sun/js/accessibility/AccessibleValueHandler.js
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- sun/js/accessibility/AccessibleValueHandler.js	(revision 2b9c6f202e653f23c8590e7a24d5dbc828ed6955)
+++ sun/js/accessibility/AccessibleValueHandler.js	(date 1567527653966)
@@ -65,6 +65,10 @@
          */
         initializeAccessibleValueHandler( valueProperty, enabledRangeProperty, enabledProperty, options ) {
 
+          assert && assert( valueProperty instanceof Property, 'should be a Property' );
+          assert && assert( enabledRangeProperty instanceof Property, 'should be a Property' );
+          assert && assert( enabledProperty instanceof Property, 'should be a Property' );
+
           // ensure that the client does not set both a custom text pattern and a text creation function
           assert && assert(
             !( options.a11yValuePattern && options.a11yCreateValueChangeAriaValueText ),
Index: inverse-square-law-common/js/view/ISLCObjectNode.js
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- inverse-square-law-common/js/view/ISLCObjectNode.js	(date 1566592619000)
+++ inverse-square-law-common/js/view/ISLCObjectNode.js	(date 1567531049259)
@@ -210,16 +210,14 @@
         // drag position relative to the pointer pointer start position and convert to model coordinates
         let x = modelViewTransform.viewToModelX( this.globalToParentPoint( event.pointer.point ).x - clickOffset );
 
-        // absolute drag bounds based on model
-        // see method descriptions for details
-        const xMax = model.getObjectMaxPosition( object );
-        const xMin = model.getObjectMinPosition( object );
+        const xMax = object.positionProperty.range.value.max;
+        const xMin = object.positionProperty.range.value.min;
 
         // apply limitations and update position
         x = Math.max( Math.min( x, xMax ), xMin ); // limited value of x (by boundary) in model coordinates
 
         // snapToGrid method dynamically checks whether to snap or not
-        object.positionProperty.set( model.snapToGrid( x ) );
+        object.deferPositionUpdateToStep =  model.snapToGrid( x );
       },
       end: () => { object.isDragging = false; },
       tandem: config.tandem.createTandem( 'dragListener' )
@@ -283,7 +281,7 @@
     // a11y - initialize the accessible slider, which makes this Node act like an accessible range input
     this.initializeAccessibleSlider(
       object.positionProperty,
-      object.enabledRangeProperty,
+      object.positionProperty.range,
       new BooleanProperty( true ), // always enabled
       accessibleSliderOptions
     );
Index: inverse-square-law-common/js/view/describers/PositionDescriber.js
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- inverse-square-law-common/js/view/describers/PositionDescriber.js	(date 1566592619000)
+++ inverse-square-law-common/js/view/describers/PositionDescriber.js	(date 1567537360270)
@@ -1,4 +1,4 @@
-// Copyright 2018-2019, University of Colorado Boulder
+  // Copyright 2018-2019, University of Colorado Boulder
 
 /**
  * Responsible for logic associated with the formation of audio description strings related to the positions of the
@@ -393,7 +393,7 @@
      */
     objectAtTouchingMin( objectEnum ) {
       const object = this.getObjectFromEnum( objectEnum );
-      return object.positionProperty.get() === object.enabledRangeProperty.get().min;
+      return object.positionProperty.get() === object.positionProperty.range.min;
     }
 
     /**
@@ -405,7 +405,7 @@
      */
     objectAtTouchingMax( objectEnum ) {
       const object = this.getObjectFromEnum( objectEnum );
-      return object.positionProperty.get() === object.enabledRangeProperty.get().max;
+      return object.positionProperty.get() === object.positionProperty.range.max;
     }
 
     /**
Index: inverse-square-law-common/js/model/ISLCObject.js
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- inverse-square-law-common/js/model/ISLCObject.js	(date 1566592619000)
+++ inverse-square-law-common/js/model/ISLCObject.js	(date 1566603465303)
@@ -9,6 +9,7 @@
   'use strict';
 
   // modules
+  const BooleanProperty = require( 'AXON/BooleanProperty' );
   const DerivedProperty = require( 'AXON/DerivedProperty' );
   const DerivedPropertyIO = require( 'AXON/DerivedPropertyIO' );
   const Emitter = require( 'AXON/Emitter' );
@@ -44,11 +45,20 @@
       valueUnits: 'kilograms'
     }, options );
 
+    const enabledRange = new Range( options.leftObjectBoundary, options.rightObjectBoundary );
+
+    const positionPropertyTandem = tandem.createTandem( 'positionProperty' );
+
     // @public
     this.positionProperty = new NumberProperty( initialPosition, {
-      tandem: tandem.createTandem( 'positionProperty' ),
+      tandem: positionPropertyTandem,
       units: 'meters',
-      range: new Range( options.leftObjectBoundary, options.rightObjectBoundary ),
+      range: new Property( enabledRange, {
+        tandem: positionPropertyTandem.createTandem( 'rangeProperty' ),
+        phetioDocumentation: 'The range for position of this object based on the radius and location of both objects',
+        phetioType: PropertyIO( RangeIO ),
+        phetioReadOnly: true
+      } ),
       phetioDocumentation: 'The position of the object along the track, in meters.',
       phetioStudioControl: false // temporary workaround until we support dynamic ranges in https://github.com/phetsims/gravity-force-lab/issues/172
     } );
@@ -74,17 +84,6 @@
       }
     );
 
-    const enabledRange = new Range( options.leftObjectBoundary, options.rightObjectBoundary );
-
-    // @public {Property.<Range>}- set by ISLCModel when the force changes, the range for position of the objects
-    // based on their radius and location
-    this.enabledRangeProperty = new Property( enabledRange, {
-      tandem: tandem.createTandem( 'enabledRangeProperty' ),
-      phetioDocumentation: 'The range for position of this object based on the radius and location of both objects',
-      phetioType: PropertyIO( RangeIO ),
-      phetioReadOnly: true
-    } );
-
     // @public (read-only) - Emitter that fires whenever the position changes as a result of an object's value changing.
     // Emits with the objectEnum that caused the position change.
     this.positionChangedFromSecondarySourceEmitter = new Emitter( { parameters: [ { valueType: ISLCObjectEnum } ] } );
@@ -100,9 +99,9 @@
     // from a value change. See https://github.com/phetsims/gravity-force-lab-basics/issues/168
     this.valueChangedSinceLastStep = false;
 
-    // @public (read-only) - flag flipped when the constant radius boolean is toggled. This is used to check if a position change is
+    // @public (read-only) {Property.<boolean>} - flipped when the constant radius boolean is toggled. This is used to check if a position change is
     // caused from the constant radius toggling. See https://github.com/phetsims/gravity-force-lab-basics/issues/168
-    this.constantRadiusChangedSinceLastStep = false;
+    this.constantRadiusChangedSinceLastStepProperty = new BooleanProperty( false );
 
     // @public
     this.valueRange = valueRange;
@@ -121,7 +120,7 @@
 
     // Link these flags to their associatedProperties
     this.valueProperty.link( () => { this.valueChangedSinceLastStep = true; } );
-    constantRadiusProperty.link( () => { this.constantRadiusChangedSinceLastStep = true; } );
+    constantRadiusProperty.link( () => { this.constantRadiusChangedSinceLastStepProperty.value = true; } );
   }
 
   inverseSquareLawCommon.register( 'ISLCObject', ISLCObject );
@@ -134,7 +133,7 @@
      */
     onStepEnd: function() {
       this.valueChangedSinceLastStep = false;
-      this.constantRadiusChangedSinceLastStep = false;
+      this.constantRadiusChangedSinceLastStepProperty.value = false;
     },
 
     /**
Index: inverse-square-law-common/js/model/ISLCModel.js
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- inverse-square-law-common/js/model/ISLCModel.js	(date 1566592619000)
+++ inverse-square-law-common/js/model/ISLCModel.js	(date 1567531103544)
@@ -87,6 +87,7 @@
       units: 'newtons',
       phetioDocumentation: 'The force of one object on the other (in Newtons)'
     } );
+    window.model = this;
 
     // @private {Property.<number>} - The distance between the two objects. Added for PhET-iO.
     this.separationProperty = new DerivedProperty( [
@@ -99,38 +100,32 @@
       phetioDocumentation: 'The distance between the two objects\' centers'
     } );
 
-    const updateRange = object => {
-      const maxPosition = this.getObjectMaxPosition( object );
-      const minPosition = this.getObjectMinPosition( object );
-
-      object.enabledRangeProperty.set( new Range( minPosition, maxPosition ) );
-    };
-
-    // a11y - necessary to reset the enabledRangeProperty to prevent object overlap, disposal not necessary
-    // We need to update the available range for each object when the either's radius or position changes.
-    Property.multilink( [ object1.positionProperty, object2.positionProperty ], () => {
-      updateRange( object1 );
-      updateRange( object2 );
-    } );
+    //
+    // // a11y - necessary to reset the positionProperty.range to prevent object overlap, disposal not necessary
+    // // We need to update the available range for each object when the either's radius or position changes.
+    // Property.multilink( [ object1.radiusProperty ], () => {
+    //   this.updateRange( object2 );
+    // } );
+    // Property.multilink( [ object2.radiusProperty ], () => {
+    //   this.updateRange( object1 );
+    // } );
+    //
+    // // a11y - necessary to reset the positionProperty.range to prevent object overlap, disposal not necessary
+    // // We need to update the available range for each object when the either's radius or position changes.
+    // Property.multilink( [ object1.positionProperty ], () => {
+    //   this.updateRange( object2 );
+    // } );
 
     // when sim is reset, we only reset the position properties of each object to their initial values
     // thus, there is no need to dispose of the listeners below
     this.object1.radiusProperty.link( () => {
       this.object1.radiusLastChanged = true;
       this.object2.radiusLastChanged = false;
-
-      // update range if radius changed with "constant radius" setting (which didn't trigger other model updates)
-      updateRange( object1 );
-      updateRange( object2 );
     } );
 
     this.object2.radiusProperty.link( () => {
       this.object2.radiusLastChanged = true;
       this.object1.radiusLastChanged = false;
-
-      // update range if radius changed with "constant radius" setting (which didn't trigger other model updates)
-      updateRange( object2 );
-      updateRange( object1 );
     } );
 
     // wire up logic to update the state of the pushedObjectEnumProperty
@@ -205,12 +200,25 @@
       locationObject1 = this.snapToGrid( locationObject1 );
       locationObject2 = this.snapToGrid( locationObject2 );
 
+      if ( this.object1.deferPositionUpdateToStep ) {
+        this.object1.positionProperty.set( this.object1.deferPositionUpdateToStep );
+        this.object1.deferPositionUpdateToStep = null;
+      }
+      if ( this.object2.deferPositionUpdateToStep ) {
+        this.object2.positionProperty.set( this.object2.deferPositionUpdateToStep );
+        this.object2.deferPositionUpdateToStep = null;
+      }
+
       if ( this.object1.isDragging ) {
         this.object1.positionProperty.set( locationObject1 );
+        this.updateRange( this.object2 );
+
       }
       else if ( this.object2.isDragging ) {
         this.object2.positionProperty.set( locationObject2 );
+        this.updateRange( this.object1 );
       }
+
       else {
 
         // neither object is dragging, radius must have changed
@@ -222,6 +230,7 @@
 
               // object2 is not at the edge update its position
               this.object2.positionProperty.set( locationObject2 );
+              this.updateRange( this.object1 );
               this.object2.positionChangedFromSecondarySourceEmitter.emit( OBJECT_ONE );
             }
           }
@@ -232,6 +241,7 @@
 
               // object2 is at the edge update object1 position
               this.object1.positionProperty.set( locationObject1 );
+              this.updateRange( this.object2 );
               this.object1.positionChangedFromSecondarySourceEmitter.emit( OBJECT_ONE );
             }
           }
@@ -244,6 +254,7 @@
 
               // object1 is not at boundary, update position
               this.object1.positionProperty.set( locationObject1 );
+              this.updateRange( this.object2 );
               this.object1.positionChangedFromSecondarySourceEmitter.emit( OBJECT_TWO );
             }
           }
@@ -253,16 +264,30 @@
             if ( locationObject2 !== this.object2.positionProperty.get() ) {
 
               this.object2.positionProperty.set( locationObject2 );
+              this.updateRange( this.object1 );
               this.object2.positionChangedFromSecondarySourceEmitter.emit( OBJECT_TWO );
             }
           }
         }
       }
+      if ( this.object1.valueChangedSinceLastStep || this.object1.constantRadiusChangedSinceLastStepProperty.value ) {
+        this.updateRange( this.object2 );
+      }
+      if ( this.object2.valueChangedSinceLastStep || this.object2.constantRadiusChangedSinceLastStepProperty.value ) {
+        this.updateRange( this.object1 );
+      }
 
       // broadcast a message that we have updated the model
       this.stepEmitter.emit();
     },
 
+    updateRange: function( object ) {
+      const maxPosition = this.getObjectMaxPosition( object );
+      const minPosition = this.getObjectMinPosition( object );
+
+      object.positionProperty.range.set( new Range( minPosition, maxPosition ) );
+    },
+
     /**
      * Helper function to for accessing and mapping force ranges in the inheriting sims' views
      * @returns {number} - positive number, representing the magnitude of the force vector
Index: axon/js/NumberProperty.js
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- axon/js/NumberProperty.js	(revision cc407fd9db9d7c16835530041f328c1d34edfa83)
+++ axon/js/NumberProperty.js	(date 1567525787075)
@@ -32,12 +32,14 @@
       options = _.extend( {
         numberType: 'FloatingPoint', // {string} see VALID_NUMBER_TYPES
 
-        // {Range|null} range
+        // {Range|Property.<Range>|null} range
         range: null
       }, options );
 
       assert && assert( _.includes( VALID_NUMBER_TYPES, options.numberType ), 'invalid numberType: ' + options.numberType );
-      assert && options.range && assert( options.range instanceof Range, 'options.range must be of type Range:' + options.range );
+      assert && options.range && assert( options.range instanceof Range ||
+                                         ( options.range instanceof Property && options.range.value instanceof Range ),
+        'options.range must be of type Range or Property.<Range>:' + options.range );
 
       // client cannot specify superclass options that are controlled by NumberProperty
       assert && assert( !options.valueType, 'NumberProperty sets valueType' );
@@ -54,7 +56,7 @@
       // @public (read-only) - used by PhET-iO in NumberPropertyIO as metadata passed to the wrapper.
       this.numberType = options.numberType;
 
-      // @public {Range|null} (read-only) - If defined, provides the range of possible values (inclusive)
+      // @public {Range|Property.<Range>|null} (read-only) - if defined, provides the range of possible values (inclusive)
       this.range = options.range;
 
       // @private {function|null} value validation that is specific to NumberProperty, null if assertions are disabled
@@ -64,7 +66,11 @@
         options.numberType === 'Integer' && validate( value, VALID_INTEGER );
 
         // validate for range
-        options.range && validate( value, { isValidValue: v => options.range.contains( v ) } );
+        if ( options.range ) {
+          const currentRange = options.range instanceof Property ? options.range.value : options.range;
+          assert && assert( currentRange instanceof Range, `unexpected Range: ${currentRange}` );
+          validate( value, { isValidValue: v => currentRange.contains( v ) } );
+        }
       } );
 
       // verify that validValues meet other NumberProperty-specific validation criteria
@@ -72,10 +78,35 @@
         options.validValues.forEach( this.assertNumberPropertyValidateValue );
       }
 
+      // @private - keep track for disposal
+      this.rangeChangeListener = () => {
+        this.assertNumberPropertyValidateValue( this.value );
+      };
+      if ( options.range && options.range instanceof Property ) {
+        options.range.link( this.rangeChangeListener );
+      }
+
       // validate initial value
       this.assertNumberPropertyValidateValue && this.assertNumberPropertyValidateValue( value );
     }
 
+    /**
+     * @public
+     */
+    reset() {
+      this.range && this.range instanceof Property && this.range.reset();
+      super.reset();
+    }
+
+    /**
+     * @public
+     * @override
+     */
+    dispose() {
+      this.range && this.range instanceof Property && this.range.unlink( this.rangeChangeListener );
+      super.dispose();
+    }
+
     /**
      * Performs value validation that is specific to NumberProperty.
      * Then sets the value and notifies listeners.
Index: axon/js/NumberPropertyIO.js
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- axon/js/NumberPropertyIO.js	(revision cc407fd9db9d7c16835530041f328c1d34edfa83)
+++ axon/js/NumberPropertyIO.js	(date 1566603465141)
@@ -13,10 +13,14 @@
   const axon = require( 'AXON/axon' );
   const NumberIO = require( 'TANDEM/types/NumberIO' );
   const phetioInherit = require( 'TANDEM/phetioInherit' );
+  const Property = require( 'AXON/Property' );
   const PropertyIO = require( 'AXON/PropertyIO' );
   const RangeIO = require( 'DOT/RangeIO' );
   const validate = require( 'AXON/validate' );
 
+  // ifphetio
+  const phetioEngine = require( 'ifphetio!PHET_IO/phetioEngine' );
+
   // constants
   const PropertyIOImpl = PropertyIO( NumberIO );
 
@@ -60,7 +64,11 @@
       }
 
       if ( numberProperty.range ) {
-        parentStateObject.range = RangeIO.toStateObject( numberProperty.range );
+        const range = numberProperty.range instanceof Property ? numberProperty.range.value : numberProperty.range;
+        parentStateObject.range = RangeIO.toStateObject( range );
+      }
+      if ( numberProperty.range instanceof Property && numberProperty.isPhetioInstrumented() ) {
+        parentStateObject.rangePhetioID = numberProperty.range.tandem.phetioID;
       }
       return parentStateObject;
     },
@@ -76,7 +84,9 @@
       fromParentStateObject.numberType = stateObject.numberType;
 
       // Create Range instance if defined, otherwise preserve value of null or undefined.
-      fromParentStateObject.range = stateObject.range ? RangeIO.fromStateObject( stateObject.range ) : stateObject.range;
+      fromParentStateObject.range = stateObject.rangePhetioID ? phetioEngine.getPhetioObject( stateObject.rangePhetioID ) :
+                                    stateObject.range ? RangeIO.fromStateObject( stateObject.range ) :
+                                    stateObject.range;
       return fromParentStateObject;
     },
 
Index: gravity-force-lab-basics/js/phet-io/gravity-force-lab-basics-phet-io-elements-baseline.js
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- gravity-force-lab-basics/js/phet-io/gravity-force-lab-basics-phet-io-elements-baseline.js	(revision 8cc6b0a29b876bf3ddb2fe76a765fbd239a9324f)
+++ gravity-force-lab-basics/js/phet-io/gravity-force-lab-basics-phet-io-elements-baseline.js	(date 1566603465285)
@@ -901,8 +901,8 @@
       "phetioStudioControl": true,
       "phetioTypeName": "DerivedPropertyIO.<NumberIO>"
     },
-    "gravityForceLabBasics.gravityForceLabBasicsScreen.model.mass1.enabledRangeProperty": {
-      "phetioDocumentation": "The range for position of this object based on the radius and location of both objects",
+    "gravityForceLabBasics.gravityForceLabBasicsScreen.model.mass1.positionProperty": {
+      "phetioDocumentation": "The position of the object along the track, in meters.",
       "phetioDynamicElement": false,
       "phetioEventType": "MODEL",
       "phetioFeatured": false,
@@ -911,10 +911,10 @@
       "phetioReadOnly": true,
       "phetioState": true,
       "phetioStudioControl": true,
-      "phetioTypeName": "PropertyIO.<RangeIO>"
+      "phetioTypeName": "NumberPropertyIO"
     },
-    "gravityForceLabBasics.gravityForceLabBasicsScreen.model.mass1.positionProperty": {
-      "phetioDocumentation": "The position of the object along the track, in meters.",
+    "gravityForceLabBasics.gravityForceLabBasicsScreen.model.mass1.positionProperty.rangeProperty": {
+      "phetioDocumentation": "The range for position of this object based on the radius and location of both objects",
       "phetioDynamicElement": false,
       "phetioEventType": "MODEL",
       "phetioFeatured": false,
@@ -932,7 +932,7 @@
       "phetioFeatured": false,
       "phetioHighFrequency": false,
       "phetioPlayback": false,
-      "phetioReadOnly": true,
+      "phetioReadOnly": false,
       "phetioState": true,
       "phetioStudioControl": true,
       "phetioTypeName": "DerivedPropertyIO.<NumberIO>"
@@ -947,10 +947,10 @@
       "phetioReadOnly": false,
       "phetioState": true,
       "phetioStudioControl": true,
-      "phetioTypeName": "NumberPropertyIO"
+      "phetioTypeName": "PropertyIO.<RangeIO>"
     },
-    "gravityForceLabBasics.gravityForceLabBasicsScreen.model.mass2.enabledRangeProperty": {
-      "phetioDocumentation": "The range for position of this object based on the radius and location of both objects",
+    "gravityForceLabBasics.gravityForceLabBasicsScreen.model.mass2.positionProperty": {
+      "phetioDocumentation": "The position of the object along the track, in meters.",
       "phetioDynamicElement": false,
       "phetioEventType": "MODEL",
       "phetioFeatured": false,
@@ -959,10 +959,10 @@
       "phetioReadOnly": true,
       "phetioState": true,
       "phetioStudioControl": true,
-      "phetioTypeName": "PropertyIO.<RangeIO>"
+      "phetioTypeName": "NumberPropertyIO"
     },
-    "gravityForceLabBasics.gravityForceLabBasicsScreen.model.mass2.positionProperty": {
-      "phetioDocumentation": "The position of the object along the track, in meters.",
+    "gravityForceLabBasics.gravityForceLabBasicsScreen.model.mass2.positionProperty.rangeProperty": {
+      "phetioDocumentation": "The range for position of this object based on the radius and location of both objects",
       "phetioDynamicElement": false,
       "phetioEventType": "MODEL",
       "phetioFeatured": false,
@@ -980,7 +980,7 @@
       "phetioFeatured": false,
       "phetioHighFrequency": false,
       "phetioPlayback": false,
-      "phetioReadOnly": true,
+      "phetioReadOnly": false,
       "phetioState": true,
       "phetioStudioControl": true,
       "phetioTypeName": "DerivedPropertyIO.<NumberIO>"
@@ -995,7 +995,7 @@
       "phetioReadOnly": false,
       "phetioState": true,
       "phetioStudioControl": true,
-      "phetioTypeName": "NumberPropertyIO"
+      "phetioTypeName": "PropertyIO.<RangeIO>"
     },
     "gravityForceLabBasics.gravityForceLabBasicsScreen.model.resetInProgressProperty": {
       "phetioDocumentation": "",
Index: coulombs-law/js/phet-io/coulombs-law-phet-io-elements-baseline.js
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- coulombs-law/js/phet-io/coulombs-law-phet-io-elements-baseline.js	(revision a78800235bba543546ef1dd07d8b52c39c57a0aa)
+++ coulombs-law/js/phet-io/coulombs-law-phet-io-elements-baseline.js	(date 1566603465223)
@@ -37,20 +37,20 @@
       "phetioStudioControl": true,
       "phetioTypeName": "PropertyIO.<BooleanIO>"
     },
-    "coulombsLaw.atomicScreen.coulombsLawAtomicScreen.model.charge1.enabledRangeProperty": {
-      "phetioDocumentation": "The range for position of this object based on the radius and location of both objects",
+    "coulombsLaw.atomicScreen.coulombsLawAtomicScreen.model.charge1.positionProperty": {
+      "phetioDocumentation": "The position of the object along the track, in meters.",
       "phetioDynamicElement": false,
       "phetioEventType": "MODEL",
       "phetioFeatured": false,
       "phetioHighFrequency": false,
       "phetioPlayback": false,
-      "phetioReadOnly": true,
+      "phetioReadOnly": false,
       "phetioState": true,
       "phetioStudioControl": true,
-      "phetioTypeName": "PropertyIO.<RangeIO>"
+      "phetioTypeName": "NumberPropertyIO"
     },
-    "coulombsLaw.atomicScreen.coulombsLawAtomicScreen.model.charge1.positionProperty": {
-      "phetioDocumentation": "The position of the object along the track, in meters.",
+    "coulombsLaw.atomicScreen.coulombsLawAtomicScreen.model.charge1.positionProperty.rangeProperty": {
+      "phetioDocumentation": "The range for position of this object based on the radius and location of both objects",
       "phetioDynamicElement": false,
       "phetioEventType": "MODEL",
       "phetioFeatured": false,
@@ -59,7 +59,7 @@
       "phetioReadOnly": false,
       "phetioState": true,
       "phetioStudioControl": false,
-      "phetioTypeName": "NumberPropertyIO"
+      "phetioTypeName": "PropertyIO.<RangeIO>"
     },
     "coulombsLaw.atomicScreen.coulombsLawAtomicScreen.model.charge1.radiusProperty": {
       "phetioDocumentation": "The radius of the object",
@@ -80,7 +80,7 @@
       "phetioFeatured": false,
       "phetioHighFrequency": false,
       "phetioPlayback": false,
-      "phetioReadOnly": false,
+      "phetioReadOnly": true,
       "phetioState": true,
       "phetioStudioControl": true,
       "phetioTypeName": "NumberPropertyIO"
@@ -109,20 +109,20 @@
       "phetioStudioControl": true,
       "phetioTypeName": "PropertyIO.<BooleanIO>"
     },
-    "coulombsLaw.atomicScreen.coulombsLawAtomicScreen.model.charge2.enabledRangeProperty": {
-      "phetioDocumentation": "The range for position of this object based on the radius and location of both objects",
+    "coulombsLaw.atomicScreen.coulombsLawAtomicScreen.model.charge2.positionProperty": {
+      "phetioDocumentation": "The position of the object along the track, in meters.",
       "phetioDynamicElement": false,
       "phetioEventType": "MODEL",
       "phetioFeatured": false,
       "phetioHighFrequency": false,
       "phetioPlayback": false,
-      "phetioReadOnly": true,
+      "phetioReadOnly": false,
       "phetioState": true,
       "phetioStudioControl": true,
-      "phetioTypeName": "PropertyIO.<RangeIO>"
+      "phetioTypeName": "NumberPropertyIO"
     },
-    "coulombsLaw.atomicScreen.coulombsLawAtomicScreen.model.charge2.positionProperty": {
-      "phetioDocumentation": "The position of the object along the track, in meters.",
+    "coulombsLaw.atomicScreen.coulombsLawAtomicScreen.model.charge2.positionProperty.rangeProperty": {
+      "phetioDocumentation": "The range for position of this object based on the radius and location of both objects",
       "phetioDynamicElement": false,
       "phetioEventType": "MODEL",
       "phetioFeatured": false,
@@ -131,7 +131,7 @@
       "phetioReadOnly": false,
       "phetioState": true,
       "phetioStudioControl": false,
-      "phetioTypeName": "NumberPropertyIO"
+      "phetioTypeName": "PropertyIO.<RangeIO>"
     },
     "coulombsLaw.atomicScreen.coulombsLawAtomicScreen.model.charge2.radiusProperty": {
       "phetioDocumentation": "The radius of the object",
@@ -152,7 +152,7 @@
       "phetioFeatured": false,
       "phetioHighFrequency": false,
       "phetioPlayback": false,
-      "phetioReadOnly": false,
+      "phetioReadOnly": true,
       "phetioState": true,
       "phetioStudioControl": true,
       "phetioTypeName": "NumberPropertyIO"
@@ -5072,10 +5072,10 @@
       "phetioFeatured": false,
       "phetioHighFrequency": false,
       "phetioPlayback": false,
-      "phetioReadOnly": false,
+      "phetioReadOnly": true,
       "phetioState": true,
       "phetioStudioControl": true,
-      "phetioTypeName": "NumberPropertyIO"
+      "phetioTypeName": "PropertyIO.<RangeIO>"
     },
     "coulombsLaw.homeScreen.view.title.pickableProperty": {
       "phetioDocumentation": "Sets whether the node will be pickable (and hence interactive), see the NodeIO documentation for more details",
@@ -5132,7 +5132,7 @@
       "phetioFeatured": false,
       "phetioHighFrequency": false,
       "phetioPlayback": false,
-      "phetioReadOnly": true,
+      "phetioReadOnly": false,
       "phetioState": true,
       "phetioStudioControl": true,
       "phetioTypeName": "PropertyIO.<BooleanIO>"
@@ -5144,7 +5144,7 @@
       "phetioFeatured": false,
       "phetioHighFrequency": false,
       "phetioPlayback": false,
-      "phetioReadOnly": true,
+      "phetioReadOnly": false,
       "phetioState": true,
       "phetioStudioControl": true,
       "phetioTypeName": "DerivedPropertyIO.<ColorIO>"
@@ -5161,8 +5161,8 @@
       "phetioStudioControl": true,
       "phetioTypeName": "PropertyIO.<BooleanIO>"
     },
-    "coulombsLaw.macroScreen.coulombsLawMacroScreen.model.charge1.enabledRangeProperty": {
-      "phetioDocumentation": "The range for position of this object based on the radius and location of both objects",
+    "coulombsLaw.macroScreen.coulombsLawMacroScreen.model.charge1.positionProperty": {
+      "phetioDocumentation": "The position of the object along the track, in meters.",
       "phetioDynamicElement": false,
       "phetioEventType": "MODEL",
       "phetioFeatured": false,
@@ -5171,10 +5171,10 @@
       "phetioReadOnly": true,
       "phetioState": true,
       "phetioStudioControl": true,
-      "phetioTypeName": "PropertyIO.<RangeIO>"
+      "phetioTypeName": "NumberPropertyIO"
     },
-    "coulombsLaw.macroScreen.coulombsLawMacroScreen.model.charge1.positionProperty": {
-      "phetioDocumentation": "The position of the object along the track, in meters.",
+    "coulombsLaw.macroScreen.coulombsLawMacroScreen.model.charge1.positionProperty.rangeProperty": {
+      "phetioDocumentation": "The range for position of this object based on the radius and location of both objects",
       "phetioDynamicElement": false,
       "phetioEventType": "MODEL",
       "phetioFeatured": false,
@@ -5207,7 +5207,7 @@
       "phetioReadOnly": false,
       "phetioState": true,
       "phetioStudioControl": true,
-      "phetioTypeName": "NumberPropertyIO"
+      "phetioTypeName": "PropertyIO.<RangeIO>"
     },
     "coulombsLaw.macroScreen.coulombsLawMacroScreen.model.charge2.baseColorProperty": {
       "phetioDocumentation": "",
@@ -5233,8 +5233,8 @@
       "phetioStudioControl": true,
       "phetioTypeName": "PropertyIO.<BooleanIO>"
     },
-    "coulombsLaw.macroScreen.coulombsLawMacroScreen.model.charge2.enabledRangeProperty": {
-      "phetioDocumentation": "The range for position of this object based on the radius and location of both objects",
+    "coulombsLaw.macroScreen.coulombsLawMacroScreen.model.charge2.positionProperty": {
+      "phetioDocumentation": "The position of the object along the track, in meters.",
       "phetioDynamicElement": false,
       "phetioEventType": "MODEL",
       "phetioFeatured": false,
@@ -5243,10 +5243,10 @@
       "phetioReadOnly": true,
       "phetioState": true,
       "phetioStudioControl": true,
-      "phetioTypeName": "PropertyIO.<RangeIO>"
+      "phetioTypeName": "NumberPropertyIO"
     },
-    "coulombsLaw.macroScreen.coulombsLawMacroScreen.model.charge2.positionProperty": {
-      "phetioDocumentation": "The position of the object along the track, in meters.",
+    "coulombsLaw.macroScreen.coulombsLawMacroScreen.model.charge2.positionProperty.rangeProperty": {
+      "phetioDocumentation": "The range for position of this object based on the radius and location of both objects",
       "phetioDynamicElement": false,
       "phetioEventType": "MODEL",
       "phetioFeatured": false,
Index: gravity-force-lab/js/phet-io/gravity-force-lab-phet-io-elements-baseline.js
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- gravity-force-lab/js/phet-io/gravity-force-lab-phet-io-elements-baseline.js	(revision 7f3203a104af39298d30f01fadeff0dbffeaf7b9)
+++ gravity-force-lab/js/phet-io/gravity-force-lab-phet-io-elements-baseline.js	(date 1567527269932)
@@ -901,29 +901,29 @@
       "phetioStudioControl": true,
       "phetioTypeName": "DerivedPropertyIO.<NumberIO>"
     },
-    "gravityForceLab.gravityForceLabScreen.model.mass1.enabledRangeProperty": {
-      "phetioDocumentation": "The range for position of this object based on the radius and location of both objects",
+    "gravityForceLab.gravityForceLabScreen.model.mass1.positionProperty": {
+      "phetioDocumentation": "The position of the object along the track, in meters.",
       "phetioDynamicElement": false,
       "phetioEventType": "MODEL",
       "phetioFeatured": false,
       "phetioHighFrequency": false,
       "phetioPlayback": false,
-      "phetioReadOnly": true,
+      "phetioReadOnly": false,
       "phetioState": true,
-      "phetioStudioControl": true,
-      "phetioTypeName": "PropertyIO.<RangeIO>"
+      "phetioStudioControl": false,
+      "phetioTypeName": "NumberPropertyIO"
     },
-    "gravityForceLab.gravityForceLabScreen.model.mass1.positionProperty": {
-      "phetioDocumentation": "The position of the object along the track, in meters.",
+    "gravityForceLab.gravityForceLabScreen.model.mass1.positionProperty.rangeProperty": {
+      "phetioDocumentation": "The range for position of this object based on the radius and location of both objects",
       "phetioDynamicElement": false,
       "phetioEventType": "MODEL",
       "phetioFeatured": false,
       "phetioHighFrequency": false,
       "phetioPlayback": false,
-      "phetioReadOnly": false,
+      "phetioReadOnly": true,
       "phetioState": true,
-      "phetioStudioControl": false,
-      "phetioTypeName": "NumberPropertyIO"
+      "phetioStudioControl": true,
+      "phetioTypeName": "PropertyIO.<RangeIO>"
     },
     "gravityForceLab.gravityForceLabScreen.model.mass1.radiusProperty": {
       "phetioDocumentation": "The radius of the object",
@@ -949,29 +949,29 @@
       "phetioStudioControl": true,
       "phetioTypeName": "NumberPropertyIO"
     },
-    "gravityForceLab.gravityForceLabScreen.model.mass2.enabledRangeProperty": {
-      "phetioDocumentation": "The range for position of this object based on the radius and location of both objects",
+    "gravityForceLab.gravityForceLabScreen.model.mass2.positionProperty": {
+      "phetioDocumentation": "The position of the object along the track, in meters.",
       "phetioDynamicElement": false,
       "phetioEventType": "MODEL",
       "phetioFeatured": false,
       "phetioHighFrequency": false,
       "phetioPlayback": false,
-      "phetioReadOnly": true,
+      "phetioReadOnly": false,
       "phetioState": true,
-      "phetioStudioControl": true,
-      "phetioTypeName": "PropertyIO.<RangeIO>"
+      "phetioStudioControl": false,
+      "phetioTypeName": "NumberPropertyIO"
     },
-    "gravityForceLab.gravityForceLabScreen.model.mass2.positionProperty": {
-      "phetioDocumentation": "The position of the object along the track, in meters.",
+    "gravityForceLab.gravityForceLabScreen.model.mass2.positionProperty.rangeProperty": {
+      "phetioDocumentation": "The range for position of this object based on the radius and location of both objects",
       "phetioDynamicElement": false,
       "phetioEventType": "MODEL",
       "phetioFeatured": false,
       "phetioHighFrequency": false,
       "phetioPlayback": false,
-      "phetioReadOnly": false,
+      "phetioReadOnly": true,
       "phetioState": true,
-      "phetioStudioControl": false,
-      "phetioTypeName": "NumberPropertyIO"
+      "phetioStudioControl": true,
+      "phetioTypeName": "PropertyIO.<RangeIO>"
     },
     "gravityForceLab.gravityForceLabScreen.model.mass2.radiusProperty": {
       "phetioDocumentation": "The radius of the object",
Index: gravity-force-lab/js/gravity-force-lab/view/GravityForceLabAlertManager.js
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- gravity-force-lab/js/gravity-force-lab/view/GravityForceLabAlertManager.js	(revision 7f3203a104af39298d30f01fadeff0dbffeaf7b9)
+++ gravity-force-lab/js/gravity-force-lab/view/GravityForceLabAlertManager.js	(date 1566603465236)
@@ -94,7 +94,7 @@
       const secondaryPositionChangedListener = objectEnum => {
 
         // handle the case where the position changed from the constant radius being toggled
-        if ( model.object1.constantRadiusChangedSinceLastStep || model.object2.constantRadiusChangedSinceLastStep ) {
+        if ( model.object1.constantRadiusChangedSinceLastStepProperty.value || model.object2.constantRadiusChangedSinceLastStepProperty.value ) {
           this.alertConstantSizeChangedPosition();
         }
         else { // value specific assumption
Index: gravity-force-lab/js/phet-io/gravity-force-lab-phet-io-types.js
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- gravity-force-lab/js/phet-io/gravity-force-lab-phet-io-types.js	(revision 7f3203a104af39298d30f01fadeff0dbffeaf7b9)
+++ gravity-force-lab/js/phet-io/gravity-force-lab-phet-io-types.js	(date 1567527270001)

@zepumph
Copy link
Member Author

zepumph commented Nov 8, 2019

I ended up committing the common-code piece of this work over in phetsims/projectile-motion#192. I'm unsure about how to proceed here, as it is pretty broken, but it seems like that faulty logic is built pretty deep into the way that ISLCModel is stepped. I'm not sure how long it would take to continue on the above patch, or if it is even worth it. I think I would like to ask designers at an upcoming phet-io meeting.

@zepumph
Copy link
Member Author

zepumph commented Dec 5, 2019

The design team did not think that trying to add the dynamic range was worth the effort.

We did think though that updating the static range to be -4.8 to 4.8 instead of -5 to 5. So that it actually represents the true min/max of the track.

We should also add to the phetioDocumentation explaining where the origin is.

@zepumph
Copy link
Member Author

zepumph commented Jan 7, 2020

We did think though that updating the static range to be -4.8 to 4.8 instead of -5 to 5. So that it actually represents the true min/max of the track.

This was being handled in BASICS but not yet in REGULAR, it was good that we noticed it.

All has now been dealt with here, closing.

@zepumph zepumph closed this as completed Jan 7, 2020
zepumph added a commit to phetsims/inverse-square-law-common that referenced this issue Feb 26, 2021
zepumph added a commit to phetsims/inverse-square-law-common that referenced this issue Mar 8, 2021
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