Skip to content

Commit 2df29ab

Browse files
authored
Merge pull request #3 from OpenPhysics/claude/simulation-review-cleanup-93jozu
Clean up simulation: fix orphaned timeline feature, dedupe, polish a11y
2 parents 437a350 + 42dd46b commit 2df29ab

23 files changed

Lines changed: 279 additions & 175 deletions

CLAUDE.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ Sim-specific context for AI assistants. General SceneryStack guidance: [OpenPhys
55
## Project
66

77
A two-screen SceneryStack simulation porting the NAAP **Solar System Models** lab,
8-
scaffolded from `TemplateSingleSim`. **Scaffolding only** — both screens are a
9-
placeholder label + Reset All; no model/physics yet.
8+
scaffolded from `TemplateSingleSim`. Both screens now have complete models and
9+
fully wired views (not scaffolding).
1010

1111
- **Ptolemaic System** (`src/ptolemaic/`) — port of the NAAP *Ptolemaic System Simulator* (`ptolemaic.swf`): the Earth-centered (geocentric) model with deferent + epicycle and the resulting view from Earth.
1212
- **Planetary Configurations** (`src/configurations/`) — port of the NAAP *Planetary Configurations Simulator* (`configurationsSimulator.swf`): the Sun-centered system and the configurations (opposition, conjunction, elongation) that Earth and another planet form.
@@ -26,10 +26,10 @@ Shared code keeps the `SolarSystemModels` prefix; per-screen code uses the
2626
| `src/i18n/StringManager.ts` | Singleton localized string accessor; per-screen name + a11y getters |
2727
| `src/main.ts` | Entry point; registers both screens with the Sim |
2828
| `src/ptolemaic/PtolemaicScreen.ts` | `Screen<PtolemaicModel, PtolemaicScreenView>` wrapper |
29-
| `src/ptolemaic/model/PtolemaicModel.ts` | Ptolemaic screen state (scaffold) |
29+
| `src/ptolemaic/model/PtolemaicModel.ts` | Ptolemaic screen state: deferent/epicycle geometry, presets, memory |
3030
| `src/ptolemaic/view/PtolemaicScreenView.ts` | Ptolemaic visuals, `screenSummaryContent` + `pdomOrder` |
3131
| `src/configurations/ConfigurationsScreen.ts` | `Screen<ConfigurationsModel, ConfigurationsScreenView>` wrapper |
32-
| `src/configurations/model/ConfigurationsModel.ts` | Configurations screen state (scaffold) |
32+
| `src/configurations/model/ConfigurationsModel.ts` | Configurations screen state: orbits, synodic events, timeline |
3333
| `src/configurations/view/ConfigurationsScreenView.ts` | Configurations visuals, `screenSummaryContent` + `pdomOrder` |
3434
| `src/preferences/solarSystemModelsQueryParameters.ts` | `QueryStringMachine` parameters |
3535
| `scripts/decompile-flash.ts` | Extract ActionScript from the NAAP Flash `.swf` sources via JPEXS FFDec (→ `NAAP/decompiled/`) |

src/SolarSystemModelsConstants.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ export const ORBIT_VIEW_SCALE = 95; // px per model unit
1212
export const ORBIT_VIEW_CENTER_X = 295; // px — model origin maps here (x)
1313
export const ORBIT_VIEW_CENTER_Y = 300; // px — model origin maps here (y)
1414
export const ZODIAC_LABEL_RADIUS = 285; // px — zodiac sign label ring
15+
export const ZODIAC_LABEL_MAX_WIDTH = 55; // px — max width of a zodiac sign label
16+
export const ZODIAC_TICK_INNER_RADIUS = 250; // px — zodiac sign boundary tick, inner end
17+
export const ZODIAC_TICK_OUTER_RADIUS = 270; // px — zodiac sign boundary tick, outer end
1518
export const ZODIAC_STRIP_HEIGHT = 80; // px — "view from Earth" strip height
1619
export const ZODIAC_STRIP_WIDTH = 600; // px — width matching AS factor 600/2π
1720

@@ -39,6 +42,8 @@ export const CONFIGURATIONS_ORBIT_CENTER_Y = 285; // px — Sun maps here (y)
3942
export const CONFIGURATIONS_ORBIT_MARGIN = 60; // px — margin around orbit area
4043
export const CONFIGURATIONS_TIMELINE_WIDTH = 210; // px
4144
export const CONFIGURATIONS_TIMELINE_HEIGHT = 350; // px
45+
export const CONFIGURATIONS_TIMELINE_CYCLE_HEIGHT = 120; // px — vertical px per synodic cycle
46+
export const CONFIGURATIONS_ELONGATION_ARC_RADIUS = 35; // px — elongation indicator arc radius
4247

4348
// ── Configurations preset orbital radii (AU) ───────────────────────────────────
4449

@@ -59,6 +64,9 @@ SolarSystemModelsNamespace.register("SolarSystemModelsConstants", {
5964
ORBIT_VIEW_CENTER_X,
6065
ORBIT_VIEW_CENTER_Y,
6166
ZODIAC_LABEL_RADIUS,
67+
ZODIAC_LABEL_MAX_WIDTH,
68+
ZODIAC_TICK_INNER_RADIUS,
69+
ZODIAC_TICK_OUTER_RADIUS,
6270
ZODIAC_STRIP_HEIGHT,
6371
ZODIAC_STRIP_WIDTH,
6472
PTOLEMAIC_DEFERENT_RADIUS,
@@ -71,6 +79,8 @@ SolarSystemModelsNamespace.register("SolarSystemModelsConstants", {
7179
CONFIGURATIONS_ORBIT_MARGIN,
7280
CONFIGURATIONS_TIMELINE_WIDTH,
7381
CONFIGURATIONS_TIMELINE_HEIGHT,
82+
CONFIGURATIONS_TIMELINE_CYCLE_HEIGHT,
83+
CONFIGURATIONS_ELONGATION_ARC_RADIUS,
7484
EPICYCLE_SIZE_RANGE,
7585
ECCENTRICITY_RANGE,
7686
MOTION_RATE_RANGE,

src/common/CelestialBodyNode.ts

Lines changed: 5 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,16 @@ import type { TReadOnlyProperty } from "scenerystack/axon";
22
import type { Vector2 } from "scenerystack/dot";
33
import type { ModelViewTransform2 } from "scenerystack/phetcommon";
44
import type { NodeOptions, TPaint } from "scenerystack/scenery";
5-
import { Circle, Node, Text } from "scenerystack/scenery";
5+
import { Circle, Node } from "scenerystack/scenery";
66

77
export type CelestialBodyNodeOptions = {
88
radius?: number;
99
fill?: TPaint;
10-
label?: string;
1110
} & NodeOptions;
1211

1312
/**
14-
* A Circle + optional label Text, auto-positioned via a model Vector2 Property
15-
* and a ModelViewTransform2. Used for Earth, Sun, planets, and markers.
13+
* A Circle, auto-positioned via a model Vector2 Property and a
14+
* ModelViewTransform2. Used for Earth, Sun, planets, and markers.
1615
*/
1716
export class CelestialBodyNode extends Node {
1817
public constructor(
@@ -22,26 +21,13 @@ export class CelestialBodyNode extends Node {
2221
) {
2322
const radius = providedOptions?.radius ?? 8;
2423
const fill = providedOptions?.fill ?? "#ffffff";
25-
const label = providedOptions?.label;
2624

2725
// Extract CelestialBodyNode-specific keys, pass remaining to super
28-
const { radius: _r, fill: _f, label: _l, ...nodeOptions } = providedOptions ?? {};
26+
const { radius: _r, fill: _f, ...nodeOptions } = providedOptions ?? {};
2927

3028
const body = new Circle(radius, { fill });
31-
const children: Node[] = [body];
3229

33-
if (label !== undefined) {
34-
children.push(
35-
new Text(label, {
36-
font: "12px sans-serif",
37-
fill: "#ffffff",
38-
centerX: 0,
39-
top: radius + 3,
40-
}),
41-
);
42-
}
43-
44-
super({ children, cursor: "default", ...nodeOptions });
30+
super({ children: [body], cursor: "default", ...nodeOptions });
4531

4632
positionProperty.link((pos) => {
4733
const viewPos = mvt.modelToViewPosition(pos);
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import type { TReadOnlyProperty } from "scenerystack/axon";
2+
import { Node, Rectangle, Text } from "scenerystack/scenery";
3+
import { PhetFont } from "scenerystack/scenery-phet";
4+
import SolarSystemModelsColors from "../SolarSystemModelsColors.js";
5+
6+
/** Wrap x into [0, width) — shared by both zodiac strips' longitude→x mapping. */
7+
export function wrapToWidth(x: number, width: number): number {
8+
return ((x % width) + width) % width;
9+
}
10+
11+
/**
12+
* Shared "view from Earth" zodiac strip chrome: background band, 12 evenly
13+
* spaced sign labels, and dividers between them. Screen-specific overlays
14+
* (constellation art, sun/planet markers, elongation labels, ...) are added
15+
* by the caller on top of this Node.
16+
*/
17+
export class ZodiacStripBackground extends Node {
18+
public constructor(width: number, height: number, signStringProperties: readonly TReadOnlyProperty<string>[]) {
19+
super();
20+
21+
const band = new Rectangle(0, 0, width, height, {
22+
fill: SolarSystemModelsColors.zodiacBandColorProperty,
23+
stroke: SolarSystemModelsColors.zodiacBorderColorProperty,
24+
lineWidth: 1,
25+
});
26+
this.addChild(band);
27+
28+
const segW = width / 12;
29+
for (let i = 0; i < 12; i++) {
30+
const label = new Text(signStringProperties[i]!, {
31+
font: new PhetFont(9),
32+
fill: SolarSystemModelsColors.zodiacLabelColorProperty,
33+
maxWidth: segW - 4,
34+
});
35+
label.centerX = (i + 0.5) * segW;
36+
label.centerY = height * 0.25;
37+
this.addChild(label);
38+
39+
if (i > 0) {
40+
const divider = new Rectangle(i * segW, 0, 1, height, {
41+
fill: SolarSystemModelsColors.zodiacDividerColorProperty,
42+
});
43+
this.addChild(divider);
44+
}
45+
}
46+
}
47+
}

src/configurations/view/ConfigurationsDisplayPanel.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { Text, VBox } from "scenerystack/scenery";
2+
import { PhetFont } from "scenerystack/scenery-phet";
23
import { Checkbox } from "scenerystack/sun";
34
import { SolarSystemModelsPanel } from "../../common/SolarSystemModelsPanel.js";
45
import { StringManager } from "../../i18n/StringManager.js";
@@ -7,7 +8,7 @@ import { PANEL_WIDTH } from "../../SolarSystemModelsConstants.js";
78
import type { ConfigurationsModel } from "../model/ConfigurationsModel.js";
89

910
const LABEL_OPTS = {
10-
font: "14px sans-serif",
11+
font: new PhetFont(14),
1112
fill: SolarSystemModelsColors.textColorProperty,
1213
maxWidth: PANEL_WIDTH - 60,
1314
} as const;

src/configurations/view/ConfigurationsElongationIndicator.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,9 @@ import type { ModelViewTransform2 } from "scenerystack/phetcommon";
55
import { Node, Path, Text } from "scenerystack/scenery";
66
import { ArrowNode, PhetFont } from "scenerystack/scenery-phet";
77
import SolarSystemModelsColors from "../../SolarSystemModelsColors.js";
8+
import { CONFIGURATIONS_ELONGATION_ARC_RADIUS } from "../../SolarSystemModelsConstants.js";
89
import type { ConfigurationsModel } from "../model/ConfigurationsModel.js";
910

10-
const ARC_RADIUS_VIEW = 35; // px — arc radius in view space
11-
1211
export class ConfigurationsElongationIndicator extends Node {
1312
public constructor(model: ConfigurationsModel, mvt: ModelViewTransform2) {
1413
super({ visibleProperty: model.showElongationAngleProperty });
@@ -74,13 +73,13 @@ export class ConfigurationsElongationIndicator extends Node {
7473
const endAngle = planetDir;
7574
// Determine sweep direction: elongDeg < 0 (East) means target is east of Sun
7675
const anticlockwise = elongDeg > 0; // W = clockwise sweep, E = anticlockwise
77-
arcShape.arc(vp1.x, vp1.y, ARC_RADIUS_VIEW, startAngle, endAngle, anticlockwise);
76+
arcShape.arc(vp1.x, vp1.y, CONFIGURATIONS_ELONGATION_ARC_RADIUS, startAngle, endAngle, anticlockwise);
7877
}
7978
arcPath.shape = arcShape;
8079

8180
// Label at midpoint angle
8281
const midAngle = (sunDir + planetDir) / 2;
83-
const labelR = ARC_RADIUS_VIEW + 14;
82+
const labelR = CONFIGURATIONS_ELONGATION_ARC_RADIUS + 14;
8483
elongLabel.string = `${Math.abs(elongDeg).toFixed(1)}° ${elongLabel_}`;
8584
elongLabel.centerX = vp1.x + labelR * Math.cos(midAngle);
8685
elongLabel.centerY = vp1.y + labelR * Math.sin(midAngle);

src/configurations/view/ConfigurationsKeyboardHelpContent.ts

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,26 @@
22
* ConfigurationsKeyboardHelpContent.ts
33
*
44
* Content for the keyboard-help dialog (the "?" button in the navigation bar).
5-
* The template's only interactions are buttons and Reset All, so a single
6-
* basic-actions section covers the available keyboard controls. Add a slider or
7-
* combo-box section here as the simulation grows.
5+
* Covers the screen's keyboard-accessible interactions: basic actions (Tab,
6+
* Reset All), the observer/target planet combo boxes, and the NumberControl
7+
* sliders (orbit radii, animation rate, pause time). Dragging the planets
8+
* (and Shift-dragging to set epoch angle) and scrubbing/clicking the timeline
9+
* are mouse/touch only — they have no keyboard equivalent, so they aren't
10+
* documented here.
811
*/
912

10-
import { BasicActionsKeyboardHelpSection, TwoColumnKeyboardHelpContent } from "scenerystack/scenery-phet";
13+
import {
14+
BasicActionsKeyboardHelpSection,
15+
ComboBoxKeyboardHelpSection,
16+
SliderControlsKeyboardHelpSection,
17+
TwoColumnKeyboardHelpContent,
18+
} from "scenerystack/scenery-phet";
1119

1220
export class ConfigurationsKeyboardHelpContent extends TwoColumnKeyboardHelpContent {
1321
public constructor() {
14-
super([new BasicActionsKeyboardHelpSection()], []);
22+
super(
23+
[new BasicActionsKeyboardHelpSection(), new ComboBoxKeyboardHelpSection()],
24+
[new SliderControlsKeyboardHelpSection()],
25+
);
1526
}
1627
}

src/configurations/view/ConfigurationsScreenSummaryContent.ts

Lines changed: 43 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,26 +13,60 @@
1313
* - currentDetailsContent — a LIVE paragraph describing current state
1414
* - interactionHintContent — a short hint on how to get started
1515
*
16-
* ── Making "current details" live ─────────────────────────────────────────────
17-
* The template has no model state, so currentDetails is a static string. In a
18-
* real sim, build a DerivedProperty over the relevant model Properties and pass
19-
* it as `currentDetailsContent` so the paragraph updates as the sim runs.
20-
* See LunarLander/src/.../LunarLanderScreenSummaryContent.ts for the pattern.
16+
* currentDetailsContent is a DerivedProperty over the observer/target planet
17+
* presets, the current time, and (if locked) the current configuration name,
18+
* so the paragraph updates as the sim runs.
2119
*/
20+
import { DerivedProperty } from "scenerystack/axon";
2221
import { ScreenSummaryContent } from "scenerystack/sim";
2322
import { StringManager } from "../../i18n/StringManager.js";
2423
import type { ConfigurationsModel } from "../model/ConfigurationsModel.js";
24+
import { PRESET_KEYS } from "../model/ConfigurationsPlanet.js";
2525

2626
export class ConfigurationsScreenSummaryContent extends ScreenSummaryContent {
27-
// `model` is unused in the template but kept in the signature so real sims can
28-
// derive a live currentDetailsContent from it without changing call sites.
29-
public constructor(_model: ConfigurationsModel) {
27+
public constructor(model: ConfigurationsModel) {
3028
const a11y = StringManager.getInstance().getConfigurationsA11yStrings();
29+
const strings = StringManager.getInstance().getConfigurationsStrings();
30+
31+
// Ordered to match PRESET_KEYS: mercury, venus, earth, mars, jupiter, saturn.
32+
const planetLabelProperties = [
33+
strings.mercuryStringProperty,
34+
strings.venusStringProperty,
35+
strings.earthStringProperty,
36+
strings.marsStringProperty,
37+
strings.jupiterStringProperty,
38+
strings.saturnStringProperty,
39+
] as const;
40+
41+
const currentDetailsProperty = new DerivedProperty(
42+
[
43+
model.preset1IndexProperty,
44+
model.preset2IndexProperty,
45+
model.timeProperty,
46+
model.currentConfigurationProperty,
47+
a11y.currentDetailsTemplateStringProperty,
48+
a11y.currentConfigurationTemplateStringProperty,
49+
...planetLabelProperties,
50+
] as const,
51+
(preset1Index, preset2Index, time, currentConfiguration, template, configTemplate, ...planetLabels) => {
52+
const earthIndex = PRESET_KEYS.indexOf("earth");
53+
const observerLabel = planetLabels[preset1Index] ?? planetLabels[earthIndex];
54+
const targetLabel = planetLabels[preset2Index] ?? planetLabels[earthIndex];
55+
const configPart = currentConfiguration === "" ? "" : configTemplate.replace("{0}", currentConfiguration);
56+
57+
return template
58+
.replace("{0}", observerLabel ?? "")
59+
.replace("{1}", targetLabel ?? "")
60+
.replace("{2}", time.toFixed(2))
61+
.replace("{3}", configPart)
62+
.trim();
63+
},
64+
);
3165

3266
super({
3367
playAreaContent: a11y.screenSummary.playAreaStringProperty,
3468
controlAreaContent: a11y.screenSummary.controlAreaStringProperty,
35-
currentDetailsContent: a11y.currentDetailsStringProperty,
69+
currentDetailsContent: currentDetailsProperty,
3670
interactionHintContent: a11y.screenSummary.interactionHintStringProperty,
3771
});
3872
}

src/configurations/view/ConfigurationsScreenView.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ export class ConfigurationsScreenView extends ScreenView {
121121
tagName: "div",
122122
focusable: true,
123123
accessibleName: a11y.controls.observerDragStringProperty,
124+
accessibleHelpText: a11y.controls.observerShiftDragStringProperty,
124125
});
125126
this.addChild(observerNode);
126127

@@ -132,6 +133,7 @@ export class ConfigurationsScreenView extends ScreenView {
132133
tagName: "div",
133134
focusable: true,
134135
accessibleName: a11y.controls.targetDragStringProperty,
136+
accessibleHelpText: a11y.controls.targetShiftDragStringProperty,
135137
});
136138
this.addChild(targetNode);
137139

@@ -190,9 +192,8 @@ export class ConfigurationsScreenView extends ScreenView {
190192
updateSunPos();
191193
};
192194

193-
Multilink.multilink(
194-
[model.semimajorAxis1Property, model.semimajorAxis2Property, s.auStringProperty] as const,
195-
() => updateOrbits(),
195+
Multilink.multilink([model.semimajorAxis1Property, model.semimajorAxis2Property, s.auStringProperty] as const, () =>
196+
updateOrbits(),
196197
);
197198

198199
// ── Zodiac strip at bottom ──────────────────────────────────────────────

src/configurations/view/ConfigurationsTimeReadout.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,9 @@ export class ConfigurationsTimeReadout extends SolarSystemModelsPanel {
4343
s.secondsStringProperty,
4444
] as const,
4545
(remaining, pausedFor, second, seconds) => {
46-
if (remaining <= 0) return "";
46+
if (remaining <= 0) {
47+
return "";
48+
}
4749
const secs = Math.ceil(remaining);
4850
const unit = secs === 1 ? second : seconds;
4951
return pausedFor.replace("{0}", String(secs)).replace("{1}", unit);

0 commit comments

Comments
 (0)