Merge remote-tracking branch 'origin/main' into visual-asset-catalog
This commit is contained in:
@@ -2323,3 +2323,7 @@ INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, chang
|
||||
INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBPTXPJ910NNR5T2R9F9NWQR', 'assigned_to', NULL, 'justine', NULL, '2026-07-25 18:11:45', '2026-07-25 18:11:45.571', '2026-07-25 18:11:45.571', NULL, '7fe3851d234f1cafb2b4fc70c1f5eed8', 2) ON CONFLICT(hash) DO NOTHING;
|
||||
INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBPTXPV39JX54HYP83RZJFDM', 'assigned_to', NULL, 'araminta', NULL, '2026-07-25 18:11:46', '2026-07-25 18:11:46.095', '2026-07-25 18:11:46.095', NULL, '0d1336d72b0807fe85c054696e914350', 2) ON CONFLICT(hash) DO NOTHING;
|
||||
INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBPTXPV39JX54HYP83RZJFDM', 'status', 'in_progress', 'review', NULL, '2026-07-25 18:17:34', '2026-07-25 18:17:34.364', '2026-07-25 18:17:34.364', NULL, 'a903761710732a5f62b742cc7ca4acac', 2) ON CONFLICT(hash) DO NOTHING;
|
||||
INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBPTXPJ910NNR5T2R9F9NWQR', 'status', 'in_progress', 'review', NULL, '2026-07-25 18:19:48', '2026-07-25 18:19:48.053', '2026-07-25 18:19:48.053', NULL, '2fa6ce22c32b396a2ea7ac1dd6f64dc0', 2) ON CONFLICT(hash) DO NOTHING;
|
||||
INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FSHSYM96N2M9GJTQMF58E4WC', 'description', 'Flagged by stig during the T-1189/T-1192 round (2026-07-25): test_step_canvas_viewer.gd has no injected disk-cache-root seam (unlike test_step_canvas_request.gd, which injects a disposable root), so viewer tests that exercise the disk-cache path against a real-looking body_id (e.g. GJ1c) read/write the REAL user://atlas_cache/ shared across runs — a stale real entry from earlier manual sessions can leak into test behavior. Current mitigation (used by the new T-1189/T-1192 tests and the existing sweep smoke test): distinctive synthetic body_ids. Proper fix: give the viewer suite the same injectable cache-root seam the request suite has (constructor/setter injection through StepCanvasRequest), and migrate the synthetic-body_id tests onto it. Small, test-only.', 'Flagged by stig during the T-1189/T-1192 round (2026-07-25): test_step_canvas_viewer.gd has no injected disk-cache-root seam (unlike test_step_canvas_request.gd, which injects a disposable root), so viewer tests that exercise the disk-cache path against a real-looking body_id (e.g. GJ1c) read/write the REAL user://atlas_cache/ shared across runs — a stale real entry from earlier manual sessions can leak into test behavior. Current mitigation (used by the new T-1189/T-1192 tests and the existing sweep smoke test): distinctive synthetic body_ids. Proper fix: give the viewer suite the same injectable cache-root seam the request suite has (constructor/setter injection through StepCanvasRequest), and migrate the synthetic-body_id tests onto it. Small, test-only.
|
||||
|
||||
First slice landed 2026-07-25 (main, 9ac98542d) via a live incident: PR #211''s gate failed on test_step_canvas_viewer''s two before-any-canvas tests because user://atlas_cache/ is machine-shared (every worktree gate, live capture driver, and play session write the same directory) and the T-1183 disk lookup short-circuits before test_mode''s no-op IPC — a concurrent GJ380c Global live capture (T-1157 eyeball work) warmed the exact key the tests assume cold. Fix: StepCanvasViewer.disk_cache_root_override threading into StepCanvasRequest''s existing test-injection seam; viewer + legend suites route construction through _make_viewer() helpers pinning isolated roots. REMAINING for this ticket: screen-constructed viewers (RegionalScreen builds its viewer internally — no override path), and any future suite constructing viewers directly should use the helper pattern; consider a gdUnit-wide guard (e.g. a shared test base or a lint) so new suites cannot silently ride the shared root.', NULL, '2026-07-25 18:45:59', '2026-07-25 18:45:59.847', '2026-07-25 18:45:59.847', NULL, 'dd331004eee53f809c993e6845b52cbd', 2) ON CONFLICT(hash) DO NOTHING;
|
||||
|
||||
@@ -3617,3 +3617,115 @@ archiving/marking those superseded, or by palette.md stating exactly what it inh
|
||||
Lead note (batch-5 activation): the toon-vs-PBR treatment call for environment props is delegated to
|
||||
araminta (art seat) this batch — her ruling lands in palette.md with rationale; surfaced to Jeroen in
|
||||
the batch report for cheap veto. VERDICT: READY.', 'review', 'medium', 'araminta', 'visual', 'D-235', '2026-06-12 10:40:59', '2026-07-25 18:17:34.364', NULL, '580bf084c1ac42555f672f44b96b6b4a', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
|
||||
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FBPTXPJ910NNR5T2R9F9NWQR', 'task', '06FB0TNSRZXCHGS16BFHSSGSV4', 'Create the visual production-status catalog; fix glb-gen production path + naming conventions', '(description follows in first append)
|
||||
|
||||
---
|
||||
Refinement (2026-06-16, /whats-next Si pass) — FULL scope (user decision). Two deliverables + conventions.
|
||||
|
||||
(1) CATALOG: instantiate the 5 docs/assets/visual/*.md files (sprites, furniture, tilesets, icons, effects) with per-category status tables. Template: docs/assets/_templates/sprite.md (SPR/FUR/TILE/ICO/FX prefixes); status vocab planned/in-progress/placeholder/final (docs/assets/README.md). Audio side (docs/assets/audio/README.md) is the working model.
|
||||
|
||||
(2) glb-gen PRODUCTION PATH: SKILL.md:115 names client/assets/models/ which does NOT exist. Create it + furniture/ + props/ subdirs (mirror the spike) + .gitkeep. Define client/assets/models/manifest.json schema MIRRORING the character manifest (docs/architecture/character-asset-organization.md) — per-asset entries: name, category, source-model path, subdirectory. Promotion convention per D-241 (promotion = repo-canonical). Update SKILL.md to point at the new path + reference the manifest.
|
||||
|
||||
(3) CONVENTIONS (author NOW, full scope): door state-frame suffixes (e.g. <model>_open / <model>_closed), Godot TileSet resource organization (<tileset>/<tile_id>.tres), multi-tile footprint metadata, and renderer/textures/ naming keyed to D-235 tokens (current renderer/README.md:80-95 covers sprites only: <model>_<direction>_<resolution>.png). Author these in docs/assets/visual/conventions.md (single home) and cross-link from the SKILL.
|
||||
|
||||
OVERLAP: T-1053 (rewrite araminta briefing) softly depends on this catalog — note the edge. T-1049''s per-class stories will reference these conventions — do T-1050 first / concurrently so T-1049 can cite them.
|
||||
|
||||
---
|
||||
HELD 2026-06-16 (separated from the cascade batch; needs re-scoping before resuming).
|
||||
The asset pipeline this ticket assumes is sprite-centric (docs/assets/visual/sprites.md
|
||||
"Entities", _templates/sprite.md, SPR/FUR/TILE/ICO/FX prefixes, the /sprite-gen
|
||||
render-3D->2D-sprites step). Direction correction: we are NOT producing sprites — Trellis
|
||||
produces 3D objects (.glb) displayed in-world; the only flattened 2D content is TEXTURES.
|
||||
Re-scope this ticket around a 3D-model + texture catalog (not a sprite catalog) before
|
||||
activating. See the sprite-vs-3D root-cause note (this session).
|
||||
|
||||
Batch-5 Si pass correction (2026-07-25): the 2026-06-16 refinement block above is INVALIDATED by the HELD note that follows it — the sprite-centric scope (SPR/FUR prefixes, sprite catalog, /sprite-gen) contradicts the recorded direction (Trellis produces 3D .glb models; only flattened 2D content is textures). Do not implement as written. A re-scope pass (3D-model + texture catalog, manifest mirroring character-asset-organization, D-241 promotion, D-235-keyed texture naming) is running as part of batch 5; T-1052 (style bible) and T-1051 (title-only) get refinement passes in the same sweep. Sequencing per T-1049''s own note stands: this ticket''s conventions land first, T-1049/T-1053 consume them.
|
||||
|
||||
Refinement (batch-5 Si pass, 2026-07-25) — SUPERSEDES the 2026-06-16 block above (sprite-centric, invalidated by D-244). READY.
|
||||
|
||||
CONTEXT: D-244 (confirmed, 2026-06-16) already re-scoped the catalog structure and repointed
|
||||
renderer/README.md, sprite-gen SKILL.md, docs/assets/README.md, docs/assets/visual/{README,palette}.md,
|
||||
and docs/assets/_templates/sprite.md (marked legacy) at the 3D-model reality. This ticket is now
|
||||
narrower than its original draft: instantiate what D-244 named but did not fill in, plus author
|
||||
conventions.md (net-new — no D-244 amendment touched it).
|
||||
|
||||
DELIVERABLE (a) — Category catalog files, per docs/assets/README.md''s already-updated structure:
|
||||
- docs/assets/visual/models.md — 3D objects/props (.glb via /glb-gen). Row template TBD (see
|
||||
conventions.md below) since _templates/sprite.md is explicitly legacy/retired for this use.
|
||||
- docs/assets/visual/textures.md — PBR surface maps (BaseColor/Normal/Roughness) for 3D models.
|
||||
- docs/assets/visual/artwork.md — flat 2D art (paintings/flags/billboards/signage) via /sprite-gen''s
|
||||
repurposed 2D-artwork role.
|
||||
- docs/assets/visual/icons.md — UI icons (13 SVGs already exist in client/assets/icons/ — this file
|
||||
should reflect that as-built inventory, not start at zero).
|
||||
- docs/assets/visual/effects.md — shaders/particles/overlays.
|
||||
Each file follows docs/assets/audio/README.md''s model: per-category status table (planned/in-progress/
|
||||
placeholder/final per docs/assets/README.md''s vocab), current counts. Update docs/assets/visual/README.md''s
|
||||
category table counts once files exist (currently all show 0, which will stay accurate at initial creation
|
||||
— do not invent placeholder rows).
|
||||
ACCEPTANCE: 5 files exist, cross-linked from visual/README.md (already has the links — verify they resolve),
|
||||
each with a real (possibly empty) status table matching the audio/README.md pattern.
|
||||
|
||||
DELIVERABLE (b) — glb-gen production path:
|
||||
- Create client/assets/models/ with furniture/ and props/ subdirectories (mirroring the proven
|
||||
spike layout at spikes/3dpipeline/models/{furniture,props}/) + .gitkeep in each.
|
||||
- Define client/assets/models/manifest.json, schema mirroring docs/architecture/character-asset-organization.md''s
|
||||
per-asset-entry pattern (that doc''s clothing/accessory entries are the closest precedent — a flat
|
||||
per-item record, not per-body-type variants since environment props aren''t body-fitted). Minimum
|
||||
fields: name, category (furniture|props), source_model (path under client/assets/models/), subdirectory,
|
||||
status (planned/in-progress/placeholder/final — reuse the docs/assets/README.md vocab so catalog and
|
||||
manifest never disagree). Promotion = repo-canonical per D-241 — an asset entering manifest.json AND
|
||||
client/assets/models/ IS the promotion act; nothing stays "promoted" in spikes/.
|
||||
- Update .claude/skills/glb-gen/SKILL.md — replace the "client/assets/models/ does not exist... that''s
|
||||
a Phase-5 concern" paragraph (lines ~96-102) with the new path + a pointer to the manifest. NOTE:
|
||||
this contradicts nothing already in D-244/T-750 — T-750''s own epic description lists furniture/props
|
||||
production as Phase-4 parallel work, so populating the production path (not populating the world with
|
||||
them — that''s Phase 5 per T-961) is in-scope now.
|
||||
ACCEPTANCE: client/assets/models/{furniture,props}/.gitkeep exist; manifest.json schema documented
|
||||
(either inline in SKILL.md or as a comment/example in the file itself, following the character
|
||||
manifest''s convention); SKILL.md''s stale "does not exist" paragraph is corrected.
|
||||
|
||||
DELIVERABLE (c) — docs/assets/visual/conventions.md (NEW FILE — no prior art to amend):
|
||||
- Model naming: <category>_<descriptor>.glb (e.g. table_baroque.glb, chair_modernist.glb) —
|
||||
mirrors the spike''s existing baroque_table.glb/modernist_chair.glb pattern loosely; pick one
|
||||
order and state it (category-first matches the character-asset clothing convention
|
||||
<category>_<descriptor>; the spike used descriptor-first — this is a naming decision to make,
|
||||
not silently pick — see lead note below on who rules it).
|
||||
- Mask sidecar: <model>_mask.png, single-channel per the character-asset convention (Section 3
|
||||
of character-asset-organization.md) UNLESS the item needs multi-region tinting, in which case follow
|
||||
the RGBA channel-routed convention from that doc''s Section 11 (T-1089) rather than reinventing one.
|
||||
- Texture naming keyed to D-235 tokens: PBR maps for building-exterior surfaces should name by
|
||||
the ratified ObjectTag vocabulary (D-235 amendment, wiki/economics/object_tag_vocabulary.toml) —
|
||||
e.g. concrete_wall_basecolor.png, pitched_roof_normal.png — NOT the old renderer/README.md
|
||||
<model>_<direction>_<resolution>.png scheme, which is direction-indexed for the retired 4-direction
|
||||
sprite render and does not apply to PBR texture maps at all (there is no "direction" for a texture,
|
||||
only map type: basecolor/normal/roughness).
|
||||
- Door state handling — DO NOT port a state-frame suffix convention. The 2026-06-16 draft proposed
|
||||
<model>_open/<model>_closed GLB variants; this is sprite-era thinking (baked state = baked frame).
|
||||
For a 3D object the door''s open/closed/locked/sealed state (T-979/T-998''s DoorSpec: Open/Closed/
|
||||
TemporalWindow/Locked/Sealed) is a RUNTIME transform/animation on one door .glb, not separate baked
|
||||
models — but the actual mechanism (AnimationPlayer swing vs. procedural hinge rotation vs. a
|
||||
door-model convention with a named "open" bone) is a Phase-5 rendering decision (player control +
|
||||
in-world rendering, per the cascade), not a Phase-4 asset-catalog concern. This ticket
|
||||
should record ONLY the negative constraint (no state-frame suffix files) and defer the positive
|
||||
mechanism to whichever Phase-5 ticket handles door rendering. Do not invent a runtime convention here.
|
||||
- Godot TileSet resource organization — OUT OF SCOPE, removed from ticket. client/scripts/rendering/
|
||||
tile_renderer.gd''s TileSet is an explicitly-labeled placeholder ("programmatic TileSet with placeholder
|
||||
colored rectangles," D-014) and the cascade rule states current in-world rendering code is
|
||||
"left as-is until Phase 5 — do not build on it or rip it out before then." A <tileset>/<tile_id>.tres
|
||||
convention would be building on Phase-5-owned code from Phase 4. Do not author this section.
|
||||
- Multi-tile footprint metadata: still relevant (a table/couch spans multiple sim tiles regardless
|
||||
of sprite vs 3D) — keep this item, express as a manifest.json field (e.g. footprint_tiles: [w, h])
|
||||
rather than a filename convention.
|
||||
ACCEPTANCE: conventions.md exists, covers model naming / mask sidecars / D-235-keyed texture naming /
|
||||
multi-tile footprint metadata; explicitly states the door-state and TileSet items are deferred (with
|
||||
reasons) rather than silently omitted, so a future refinement pass doesn''t have to re-derive why.
|
||||
|
||||
SEQUENCING (unchanged from 2026-06-16 note): T-1053 (araminta briefing rewrite) softly depends on this
|
||||
catalog. T-1049''s per-class stories reference these conventions — this ticket lands first / concurrently.
|
||||
|
||||
Lead note (batch-5 activation): the model-naming-order call (category-first vs descriptor-first) is
|
||||
delegated to araminta (art seat) this batch — her ruling lands in conventions.md with a one-line
|
||||
rationale; surfaced to Jeroen in the batch report for cheap veto. VERDICT: READY.', 'review', 'high', 'justine', 'visual', NULL, '2026-06-12 10:40:59', '2026-07-25 18:19:48.052', NULL, '111c7b03cb5bc345c306a64fcd8a76d0', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
|
||||
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FSHSYM96N2M9GJTQMF58E4WC', 'task', '06FBPPMZNNEV052DBYYY3A897C', 'StepCanvasViewer test suite lacks an injected disk-cache-root seam — tests share the real user://atlas_cache', 'Flagged by stig during the T-1189/T-1192 round (2026-07-25): test_step_canvas_viewer.gd has no injected disk-cache-root seam (unlike test_step_canvas_request.gd, which injects a disposable root), so viewer tests that exercise the disk-cache path against a real-looking body_id (e.g. GJ1c) read/write the REAL user://atlas_cache/ shared across runs — a stale real entry from earlier manual sessions can leak into test behavior. Current mitigation (used by the new T-1189/T-1192 tests and the existing sweep smoke test): distinctive synthetic body_ids. Proper fix: give the viewer suite the same injectable cache-root seam the request suite has (constructor/setter injection through StepCanvasRequest), and migrate the synthetic-body_id tests onto it. Small, test-only.
|
||||
|
||||
First slice landed 2026-07-25 (main, 9ac98542d) via a live incident: PR #211''s gate failed on test_step_canvas_viewer''s two before-any-canvas tests because user://atlas_cache/ is machine-shared (every worktree gate, live capture driver, and play session write the same directory) and the T-1183 disk lookup short-circuits before test_mode''s no-op IPC — a concurrent GJ380c Global live capture (T-1157 eyeball work) warmed the exact key the tests assume cold. Fix: StepCanvasViewer.disk_cache_root_override threading into StepCanvasRequest''s existing test-injection seam; viewer + legend suites route construction through _make_viewer() helpers pinning isolated roots. REMAINING for this ticket: screen-constructed viewers (RegionalScreen builds its viewer internally — no override path), and any future suite constructing viewers directly should use the helper pattern; consider a gdUnit-wide guard (e.g. a shared test base or a lint) so new suites cannot silently ride the shared root.', 'backlog', 'low', NULL, 'client', NULL, '2026-07-25 10:52:37.833', '2026-07-25 18:45:59.847', NULL, '00c87aff1d2d9c4406944b52fd04c6f0', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
|
||||
|
||||
@@ -9,9 +9,20 @@ extends GdUnitTestSuite
|
||||
const LegendScript := preload("res://ui/implant/apps/atlas/step_canvas/step_canvas_legend.gd")
|
||||
const StepCanvasTransport := preload("res://ui/implant/apps/atlas/step_canvas/step_canvas_transport.gd")
|
||||
|
||||
## Same Tier-2/3 isolation as test_step_canvas_viewer.gd's _make_viewer()
|
||||
## (T-1193 first slice): the default disk-cache root is machine-shared, and a
|
||||
## warm cache delivers canvases into tests entered on real body ids.
|
||||
const TEST_DISK_CACHE_ROOT := "user://test_step_canvas_legend_cache/"
|
||||
|
||||
|
||||
func _make_viewer() -> StepCanvasViewer:
|
||||
var v: StepCanvasViewer = auto_free(StepCanvasViewer.new())
|
||||
v.disk_cache_root_override = TEST_DISK_CACHE_ROOT
|
||||
return v
|
||||
|
||||
|
||||
func test_legend_starts_hidden_before_refresh() -> void:
|
||||
var v: StepCanvasViewer = auto_free(StepCanvasViewer.new())
|
||||
var v: StepCanvasViewer = _make_viewer()
|
||||
add_child(v)
|
||||
var legend = LegendScript.new(v)
|
||||
auto_free(legend)
|
||||
@@ -19,7 +30,7 @@ func test_legend_starts_hidden_before_refresh() -> void:
|
||||
|
||||
|
||||
func test_refresh_makes_the_legend_visible_and_populates_content() -> void:
|
||||
var v: StepCanvasViewer = auto_free(StepCanvasViewer.new())
|
||||
var v: StepCanvasViewer = _make_viewer()
|
||||
add_child(v)
|
||||
v.enter({"body_id": "GJ380c", "body_radius_km": 6238.4}, {})
|
||||
var legend = LegendScript.new(v)
|
||||
@@ -45,7 +56,7 @@ func test_refresh_without_a_viewer_does_not_crash() -> void:
|
||||
## default, one section per active toggle" contract the retired
|
||||
## atlas_window_legend.gd's own refresh() doc described.
|
||||
func test_refresh_reflects_the_active_overlay_toggle() -> void:
|
||||
var v: StepCanvasViewer = auto_free(StepCanvasViewer.new())
|
||||
var v: StepCanvasViewer = _make_viewer()
|
||||
add_child(v)
|
||||
v.enter({"body_id": "GJ380c", "body_radius_km": 6238.4}, {})
|
||||
var legend = LegendScript.new(v)
|
||||
@@ -63,7 +74,7 @@ func test_refresh_reflects_the_active_overlay_toggle() -> void:
|
||||
|
||||
|
||||
func test_reposition_sets_a_fixed_panel_margin_position() -> void:
|
||||
var v: StepCanvasViewer = auto_free(StepCanvasViewer.new())
|
||||
var v: StepCanvasViewer = _make_viewer()
|
||||
add_child(v)
|
||||
var legend = LegendScript.new(v)
|
||||
auto_free(legend)
|
||||
@@ -81,7 +92,7 @@ func test_reposition_sets_a_fixed_panel_margin_position() -> void:
|
||||
## column, with room to spare — never right up against it, and certainly
|
||||
## never past it into where the canvas is centered from.
|
||||
func test_legend_actual_right_edge_stays_inside_the_reserved_column() -> void:
|
||||
var v: StepCanvasViewer = auto_free(StepCanvasViewer.new())
|
||||
var v: StepCanvasViewer = _make_viewer()
|
||||
add_child(v)
|
||||
var legend = LegendScript.new(v)
|
||||
auto_free(legend)
|
||||
|
||||
@@ -16,16 +16,34 @@ const StepCanvasTransport := preload("res://ui/implant/apps/atlas/step_canvas/st
|
||||
## .cache/screenshots/t1183-eyeball-run2/02-region.png.
|
||||
const GJ1C_GLOBAL_EXTENT := Vector2i(177, 88)
|
||||
|
||||
## Isolated Tier-2/3 disk-cache root for every directly-constructed viewer
|
||||
## (T-1193 first slice). The default user://atlas_cache/ is SHARED across the
|
||||
## whole machine — every worktree's gate run, live capture driver, and real
|
||||
## play session writes the same directory — and T-1183's disk-cache lookup
|
||||
## short-circuits BEFORE test_mode's silent-no-op IPC, so a warm shared cache
|
||||
## delivers real canvases into tests written against "nothing ever arrives"
|
||||
## (2026-07-25: a concurrent live GJ380c Global capture flipped the two
|
||||
## before-any-canvas tests in another worktree's gate run). In test_mode the
|
||||
## isolated root stays empty forever: no canvas ever arrives, so the cache
|
||||
## never writes.
|
||||
const TEST_DISK_CACHE_ROOT := "user://test_step_canvas_viewer_cache/"
|
||||
|
||||
|
||||
func _make_viewer() -> StepCanvasViewer:
|
||||
var v: StepCanvasViewer = auto_free(StepCanvasViewer.new())
|
||||
v.disk_cache_root_override = TEST_DISK_CACHE_ROOT
|
||||
return v
|
||||
|
||||
|
||||
func test_enter_lands_on_the_global_opener() -> void:
|
||||
var v: StepCanvasViewer = auto_free(StepCanvasViewer.new())
|
||||
var v: StepCanvasViewer = _make_viewer()
|
||||
add_child(v)
|
||||
v.enter({"body_id": "GJ380c", "body_radius_km": 6238.4}, {})
|
||||
assert_str(v.get_held_rung()).is_equal(StepCanvasTransport.RUNG_GLOBAL)
|
||||
|
||||
|
||||
func test_get_body_id_reflects_the_entered_body() -> void:
|
||||
var v: StepCanvasViewer = auto_free(StepCanvasViewer.new())
|
||||
var v: StepCanvasViewer = _make_viewer()
|
||||
add_child(v)
|
||||
assert_str(v.get_body_id()).is_equal("")
|
||||
v.enter({"body_id": "GJ380c", "body_radius_km": 6238.4}, {})
|
||||
@@ -35,7 +53,7 @@ func test_get_body_id_reflects_the_entered_body() -> void:
|
||||
## Scrolling one notch descends the ladder — cursor-anchored, so a cursor
|
||||
## position must be supplied; the rung index advances by exactly one.
|
||||
func test_scroll_rung_descends_one_notch() -> void:
|
||||
var v: StepCanvasViewer = auto_free(StepCanvasViewer.new())
|
||||
var v: StepCanvasViewer = _make_viewer()
|
||||
add_child(v)
|
||||
v.enter({"body_id": "GJ380c", "body_radius_km": 6238.4}, {})
|
||||
v._scroll_rung(1, Vector2(400.0, 300.0))
|
||||
@@ -43,7 +61,7 @@ func test_scroll_rung_descends_one_notch() -> void:
|
||||
|
||||
|
||||
func test_scroll_rung_clamps_at_the_deepest_rung() -> void:
|
||||
var v: StepCanvasViewer = auto_free(StepCanvasViewer.new())
|
||||
var v: StepCanvasViewer = _make_viewer()
|
||||
add_child(v)
|
||||
v.enter({"body_id": "GJ380c", "body_radius_km": 6238.4}, {})
|
||||
for _i in range(10):
|
||||
@@ -52,7 +70,7 @@ func test_scroll_rung_clamps_at_the_deepest_rung() -> void:
|
||||
|
||||
|
||||
func test_reset_to_global_returns_from_a_deep_rung() -> void:
|
||||
var v: StepCanvasViewer = auto_free(StepCanvasViewer.new())
|
||||
var v: StepCanvasViewer = _make_viewer()
|
||||
add_child(v)
|
||||
v.enter({"body_id": "GJ380c", "body_radius_km": 6238.4}, {})
|
||||
v._scroll_rung(1, Vector2(400.0, 300.0))
|
||||
@@ -71,7 +89,7 @@ func test_reset_to_global_returns_from_a_deep_rung() -> void:
|
||||
## regression (scroll_step() clamping at index 0 meant _scroll_rung()
|
||||
## returned before ever reaching a reset call).
|
||||
func test_scroll_out_at_global_after_a_pan_resets_the_view() -> void:
|
||||
var v: StepCanvasViewer = auto_free(StepCanvasViewer.new())
|
||||
var v: StepCanvasViewer = _make_viewer()
|
||||
add_child(v)
|
||||
v.enter({"body_id": "GJ380c", "body_radius_km": 6238.4}, {})
|
||||
assert_str(v.get_held_rung()).is_equal(StepCanvasTransport.RUNG_GLOBAL)
|
||||
@@ -94,7 +112,7 @@ func test_scroll_out_at_global_after_a_pan_resets_the_view() -> void:
|
||||
## (un-drifted) Global frame must stay a no-op — the reset is edge-triggered
|
||||
## on genuine drift, not a per-scroll unconditional reset.
|
||||
func test_scroll_out_at_undrifted_global_is_a_no_op() -> void:
|
||||
var v: StepCanvasViewer = auto_free(StepCanvasViewer.new())
|
||||
var v: StepCanvasViewer = _make_viewer()
|
||||
add_child(v)
|
||||
v.enter({"body_id": "GJ380c", "body_radius_km": 6238.4}, {})
|
||||
v._scroll_rung(-1, Vector2(400.0, 300.0))
|
||||
@@ -103,21 +121,21 @@ func test_scroll_out_at_undrifted_global_is_a_no_op() -> void:
|
||||
|
||||
|
||||
func test_overlay_visibility_defaults_to_off_for_every_toggle() -> void:
|
||||
var v: StepCanvasViewer = auto_free(StepCanvasViewer.new())
|
||||
var v: StepCanvasViewer = _make_viewer()
|
||||
add_child(v)
|
||||
for def: Dictionary in v.get_overlay_defs():
|
||||
assert_bool(v.is_overlay_visible(def["id"])).is_false()
|
||||
|
||||
|
||||
func test_set_overlay_visible_updates_state() -> void:
|
||||
var v: StepCanvasViewer = auto_free(StepCanvasViewer.new())
|
||||
var v: StepCanvasViewer = _make_viewer()
|
||||
add_child(v)
|
||||
v.set_overlay_visible("gen_dw_temp", true)
|
||||
assert_bool(v.is_overlay_visible("gen_dw_temp")).is_true()
|
||||
|
||||
|
||||
func test_set_overlay_visible_unknown_id_is_a_no_op() -> void:
|
||||
var v: StepCanvasViewer = auto_free(StepCanvasViewer.new())
|
||||
var v: StepCanvasViewer = _make_viewer()
|
||||
add_child(v)
|
||||
v.set_overlay_visible("not_a_real_overlay", true)
|
||||
assert_bool(v.is_overlay_visible("not_a_real_overlay")).is_false()
|
||||
@@ -201,7 +219,7 @@ static func _synthetic_canvas(width: int, height: int) -> Dictionary:
|
||||
|
||||
|
||||
func test_maybe_refloat_is_inert_before_any_canvas_has_arrived() -> void:
|
||||
var v: StepCanvasViewer = auto_free(StepCanvasViewer.new())
|
||||
var v: StepCanvasViewer = _make_viewer()
|
||||
add_child(v)
|
||||
v.enter({"body_id": "GJ380c", "body_radius_km": 6238.4}, {})
|
||||
v._scroll_rung(1, Vector2(400.0, 300.0)) # District — footprint still ZERO, nothing arrived
|
||||
@@ -214,7 +232,7 @@ func test_maybe_refloat_is_inert_before_any_canvas_has_arrived() -> void:
|
||||
## the held canvas keeps drawing, no re-request (§4/§5's "only when a pan
|
||||
## carries the view past the held window's edge").
|
||||
func test_maybe_refloat_does_not_refloat_on_a_small_pan() -> void:
|
||||
var v: StepCanvasViewer = auto_free(StepCanvasViewer.new())
|
||||
var v: StepCanvasViewer = _make_viewer()
|
||||
add_child(v)
|
||||
v.size = Vector2(800.0, 600.0) # a real viewport size — _maybe_refloat's
|
||||
# drift math is relative to get_rect().size's own center; leaving this at
|
||||
@@ -244,7 +262,7 @@ func test_maybe_refloat_does_not_refloat_on_a_small_pan() -> void:
|
||||
## world_center, view_offset reset to ZERO (the canvas re-centers under the
|
||||
## new request).
|
||||
func test_maybe_refloat_refloats_once_the_pan_crosses_the_edge_threshold() -> void:
|
||||
var v: StepCanvasViewer = auto_free(StepCanvasViewer.new())
|
||||
var v: StepCanvasViewer = _make_viewer()
|
||||
add_child(v)
|
||||
v.enter({"body_id": "GJ380c", "body_radius_km": 6238.4}, {})
|
||||
v._scroll_rung(1, Vector2(400.0, 300.0)) # District
|
||||
@@ -266,7 +284,7 @@ func test_maybe_refloat_refloats_once_the_pan_crosses_the_edge_threshold() -> vo
|
||||
## Global never re-floats on pan (D-255(a): its canvas is the whole body,
|
||||
## no edge to cross) — even with a real texture held and a huge drift.
|
||||
func test_maybe_refloat_is_a_no_op_at_global_rung() -> void:
|
||||
var v: StepCanvasViewer = auto_free(StepCanvasViewer.new())
|
||||
var v: StepCanvasViewer = _make_viewer()
|
||||
add_child(v)
|
||||
v.enter({"body_id": "GJ380c", "body_radius_km": 6238.4}, {})
|
||||
v._terrain_layer.rebuild_from_canvas(_synthetic_canvas(200, 100), v.get_held_rung(), "")
|
||||
@@ -284,7 +302,7 @@ func test_maybe_refloat_is_a_no_op_at_global_rung() -> void:
|
||||
|
||||
|
||||
func test_edge_scroll_suppressed_without_application_focus() -> void:
|
||||
var v: StepCanvasViewer = auto_free(StepCanvasViewer.new())
|
||||
var v: StepCanvasViewer = _make_viewer()
|
||||
add_child(v)
|
||||
v.size = Vector2(800.0, 600.0)
|
||||
v._app_has_focus = false
|
||||
@@ -293,7 +311,7 @@ func test_edge_scroll_suppressed_without_application_focus() -> void:
|
||||
|
||||
|
||||
func test_edge_scroll_suppressed_when_cursor_has_never_moved_over_the_control() -> void:
|
||||
var v: StepCanvasViewer = auto_free(StepCanvasViewer.new())
|
||||
var v: StepCanvasViewer = _make_viewer()
|
||||
add_child(v)
|
||||
v.size = Vector2(800.0, 600.0)
|
||||
# _last_mouse_pos defaults to (-1, -1) — an impossible in-bounds position,
|
||||
@@ -303,7 +321,7 @@ func test_edge_scroll_suppressed_when_cursor_has_never_moved_over_the_control()
|
||||
|
||||
|
||||
func test_edge_scroll_active_near_the_left_edge() -> void:
|
||||
var v: StepCanvasViewer = auto_free(StepCanvasViewer.new())
|
||||
var v: StepCanvasViewer = _make_viewer()
|
||||
add_child(v)
|
||||
v.size = Vector2(800.0, 600.0)
|
||||
v._app_has_focus = true
|
||||
@@ -315,7 +333,7 @@ func test_edge_scroll_active_near_the_left_edge() -> void:
|
||||
|
||||
|
||||
func test_edge_scroll_active_near_the_right_edge() -> void:
|
||||
var v: StepCanvasViewer = auto_free(StepCanvasViewer.new())
|
||||
var v: StepCanvasViewer = _make_viewer()
|
||||
add_child(v)
|
||||
v.size = Vector2(800.0, 600.0)
|
||||
v._app_has_focus = true
|
||||
@@ -325,7 +343,7 @@ func test_edge_scroll_active_near_the_right_edge() -> void:
|
||||
|
||||
|
||||
func test_edge_scroll_inactive_well_inside_the_viewport() -> void:
|
||||
var v: StepCanvasViewer = auto_free(StepCanvasViewer.new())
|
||||
var v: StepCanvasViewer = _make_viewer()
|
||||
add_child(v)
|
||||
v.size = Vector2(800.0, 600.0)
|
||||
v._app_has_focus = true
|
||||
@@ -345,7 +363,7 @@ func test_edge_scroll_inactive_well_inside_the_viewport() -> void:
|
||||
## ever cached under it, so the sweep is a true no-op read (no writes to the
|
||||
## real user://atlas_cache/ directory this test could leak).
|
||||
func test_enter_runs_the_disk_cache_visit_sweep_without_crashing() -> void:
|
||||
var v: StepCanvasViewer = auto_free(StepCanvasViewer.new())
|
||||
var v: StepCanvasViewer = _make_viewer()
|
||||
add_child(v)
|
||||
v.enter({"body_id": "T1183_sweep_smoke_test_body", "body_radius_km": 6238.4}, {})
|
||||
# If the wiring is broken (e.g. calling a method that doesn't exist), the
|
||||
@@ -358,7 +376,7 @@ func test_enter_runs_the_disk_cache_visit_sweep_without_crashing() -> void:
|
||||
## Timer node, not a _process()-driven counter), autostarts, and is set to
|
||||
## the documented coarse interval — never a sub-frame or per-frame value.
|
||||
func test_disk_sweep_timer_is_coarse_and_autostarts() -> void:
|
||||
var v: StepCanvasViewer = auto_free(StepCanvasViewer.new())
|
||||
var v: StepCanvasViewer = _make_viewer()
|
||||
add_child(v)
|
||||
var timer: Timer = v.get_node("DiskSweepTimer")
|
||||
assert_object(timer).is_not_null()
|
||||
@@ -383,7 +401,7 @@ func test_disk_sweep_timer_is_coarse_and_autostarts() -> void:
|
||||
## non-blocking test speed) against an injected-root request so this test
|
||||
## touches no real cache files.
|
||||
func test_disk_sweep_timeout_handler_runs_background_sweep_for_the_current_body() -> void:
|
||||
var v: StepCanvasViewer = auto_free(StepCanvasViewer.new())
|
||||
var v: StepCanvasViewer = _make_viewer()
|
||||
add_child(v)
|
||||
v.enter({"body_id": "T1183_sweep_smoke_test_body", "body_radius_km": 6238.4}, {})
|
||||
# No live server, no cached entries for this body — the assertion is
|
||||
@@ -422,7 +440,7 @@ static func _land_global_canvas(v: StepCanvasViewer, width: int, height: int) ->
|
||||
|
||||
|
||||
func test_global_canvas_arrival_populates_the_body_extent_cap_source() -> void:
|
||||
var v: StepCanvasViewer = auto_free(StepCanvasViewer.new())
|
||||
var v: StepCanvasViewer = _make_viewer()
|
||||
add_child(v)
|
||||
v.enter({"body_id": "T1189_extent_letterbox_test_body", "body_radius_km": 6371.0}, {})
|
||||
TestStepCanvasViewer._land_global_canvas(v, GJ1C_GLOBAL_EXTENT.x, GJ1C_GLOBAL_EXTENT.y)
|
||||
@@ -434,7 +452,7 @@ func test_global_canvas_arrival_populates_the_body_extent_cap_source() -> void:
|
||||
## scrolling to Region and firing its request must produce a CAPPED extent
|
||||
## — never the raw viewport-fit 384x216 that overran the body on both axes.
|
||||
func test_region_request_extent_is_capped_to_the_landed_global_extent() -> void:
|
||||
var v: StepCanvasViewer = auto_free(StepCanvasViewer.new())
|
||||
var v: StepCanvasViewer = _make_viewer()
|
||||
add_child(v)
|
||||
v.size = Vector2(1920.0, 1080.0) # the T-1183 eyeball's own viewport
|
||||
v.enter({"body_id": "T1189_extent_letterbox_test_body", "body_radius_km": 6371.0}, {})
|
||||
@@ -457,7 +475,7 @@ func test_region_request_extent_is_capped_to_the_landed_global_extent() -> void:
|
||||
## ZERO — the Region request must go out UNCAPPED (server clamps
|
||||
## independently) rather than silently collapsing to a zero-cell request.
|
||||
func test_region_request_extent_is_uncapped_before_the_global_echo_lands() -> void:
|
||||
var v: StepCanvasViewer = auto_free(StepCanvasViewer.new())
|
||||
var v: StepCanvasViewer = _make_viewer()
|
||||
add_child(v)
|
||||
v.size = Vector2(1920.0, 1080.0)
|
||||
v.enter({"body_id": "T1189_extent_letterbox_test_body", "body_radius_km": 6371.0}, {})
|
||||
@@ -481,7 +499,7 @@ func test_region_request_extent_is_uncapped_before_the_global_echo_lands() -> vo
|
||||
## same key, capped or not, never a key built from one extent and served
|
||||
## under another.
|
||||
func test_capped_extent_matches_what_the_request_actually_sends() -> void:
|
||||
var v: StepCanvasViewer = auto_free(StepCanvasViewer.new())
|
||||
var v: StepCanvasViewer = _make_viewer()
|
||||
add_child(v)
|
||||
v.size = Vector2(1920.0, 1080.0)
|
||||
v.enter({"body_id": "T1189_extent_letterbox_test_body", "body_radius_km": 6371.0}, {})
|
||||
@@ -515,7 +533,7 @@ func test_capped_extent_matches_what_the_request_actually_sends() -> void:
|
||||
## proving the cap is spacing-keyed, not applied indiscriminately to every
|
||||
## rung once a Global extent is known.
|
||||
func test_district_request_extent_is_never_capped_by_the_global_extent() -> void:
|
||||
var v: StepCanvasViewer = auto_free(StepCanvasViewer.new())
|
||||
var v: StepCanvasViewer = _make_viewer()
|
||||
add_child(v)
|
||||
v.size = Vector2(1920.0, 1080.0)
|
||||
v.enter({"body_id": "T1189_extent_letterbox_test_body", "body_radius_km": 6371.0}, {})
|
||||
@@ -538,7 +556,7 @@ func test_district_request_extent_is_never_capped_by_the_global_extent() -> void
|
||||
## region grid") — this proves it, through the real enter()/land/enter()
|
||||
## sequence rather than asserting the field directly only.
|
||||
func test_global_body_extent_resets_on_a_different_body_and_does_not_leak() -> void:
|
||||
var v: StepCanvasViewer = auto_free(StepCanvasViewer.new())
|
||||
var v: StepCanvasViewer = _make_viewer()
|
||||
add_child(v)
|
||||
v.size = Vector2(1920.0, 1080.0)
|
||||
|
||||
@@ -575,7 +593,7 @@ func test_global_body_extent_resets_on_a_different_body_and_does_not_leak() -> v
|
||||
## have computed a non-zero centering offset (unless the canvas happens to
|
||||
## exactly fill the viewport, not the case here).
|
||||
func test_global_canvas_arrival_centers_the_view_not_top_left() -> void:
|
||||
var v: StepCanvasViewer = auto_free(StepCanvasViewer.new())
|
||||
var v: StepCanvasViewer = _make_viewer()
|
||||
add_child(v)
|
||||
v.size = Vector2(1920.0, 1080.0)
|
||||
v.enter({"body_id": "T1189_extent_letterbox_test_body", "body_radius_km": 6371.0}, {})
|
||||
@@ -596,7 +614,7 @@ func test_global_canvas_arrival_centers_the_view_not_top_left() -> void:
|
||||
## legend-reserved 1920x1080 case (R=9, scale=1.8) AND the 4K case (R=20,
|
||||
## scale=4.0) — the SAME formula, no separate coverage-threshold branch.
|
||||
func test_global_canvas_scale_yields_an_exact_integer_pixels_per_gridunit_ratio() -> void:
|
||||
var v: StepCanvasViewer = auto_free(StepCanvasViewer.new())
|
||||
var v: StepCanvasViewer = _make_viewer()
|
||||
add_child(v)
|
||||
v.size = Vector2(1920.0, 1080.0)
|
||||
v.enter({"body_id": "T1189_extent_letterbox_test_body", "body_radius_km": 6371.0}, {})
|
||||
@@ -618,7 +636,7 @@ func test_global_canvas_scale_yields_an_exact_integer_pixels_per_gridunit_ratio(
|
||||
## too (20/5 = 4.0) — confirms the 1080p case above isn't a coincidence of
|
||||
## a small viewport, just the same formula at a different achievable ratio.
|
||||
func test_global_canvas_scale_at_4k_also_yields_an_exact_integer_ratio() -> void:
|
||||
var v: StepCanvasViewer = auto_free(StepCanvasViewer.new())
|
||||
var v: StepCanvasViewer = _make_viewer()
|
||||
add_child(v)
|
||||
v.size = Vector2(3840.0, 2160.0)
|
||||
v.enter({"body_id": "T1189_extent_letterbox_test_body", "body_radius_km": 6371.0}, {})
|
||||
@@ -633,7 +651,7 @@ func test_global_canvas_scale_at_4k_also_yields_an_exact_integer_ratio() -> void
|
||||
## stays 1.0 once the player has descended past Global, even though a
|
||||
## Global canvas was landed earlier in the same session.
|
||||
func test_fixed_rung_canvas_scale_stays_one_after_descending_from_global() -> void:
|
||||
var v: StepCanvasViewer = auto_free(StepCanvasViewer.new())
|
||||
var v: StepCanvasViewer = _make_viewer()
|
||||
add_child(v)
|
||||
v.size = Vector2(1920.0, 1080.0)
|
||||
v.enter({"body_id": "T1189_extent_letterbox_test_body", "body_radius_km": 6371.0}, {})
|
||||
@@ -654,7 +672,7 @@ func test_fixed_rung_canvas_scale_stays_one_after_descending_from_global() -> vo
|
||||
## and centered, its drawn rect's LEFT edge must be at or past the legend's
|
||||
## own right edge, never underneath it.
|
||||
func test_global_canvas_left_edge_never_overlaps_the_legend_column() -> void:
|
||||
var v: StepCanvasViewer = auto_free(StepCanvasViewer.new())
|
||||
var v: StepCanvasViewer = _make_viewer()
|
||||
add_child(v)
|
||||
v.size = Vector2(1920.0, 1080.0)
|
||||
v.enter({"body_id": "T1189_extent_letterbox_test_body", "body_radius_km": 6371.0}, {})
|
||||
@@ -671,7 +689,7 @@ func test_global_canvas_left_edge_never_overlaps_the_legend_column() -> void:
|
||||
## one — _notification(NOTIFICATION_RESIZED) wires _recompute_canvas_transform()
|
||||
## for exactly this case.
|
||||
func test_resize_recenters_an_already_held_global_canvas() -> void:
|
||||
var v: StepCanvasViewer = auto_free(StepCanvasViewer.new())
|
||||
var v: StepCanvasViewer = _make_viewer()
|
||||
add_child(v)
|
||||
v.size = Vector2(1920.0, 1080.0)
|
||||
v.enter({"body_id": "T1189_extent_letterbox_test_body", "body_radius_km": 6371.0}, {})
|
||||
@@ -695,7 +713,7 @@ func test_resize_recenters_an_already_held_global_canvas() -> void:
|
||||
## under 1.0, end-to-end through the real viewer wiring, not just the pure
|
||||
## transport function this mirrors.
|
||||
func test_global_canvas_scale_never_drops_below_native_on_a_narrow_viewport() -> void:
|
||||
var v: StepCanvasViewer = auto_free(StepCanvasViewer.new())
|
||||
var v: StepCanvasViewer = _make_viewer()
|
||||
add_child(v)
|
||||
v.size = Vector2(348.0 + StepCanvasTransport.LEGEND_COLUMN_PX, 1080.0)
|
||||
v.enter({"body_id": "T1189_extent_letterbox_test_body", "body_radius_km": 6371.0}, {})
|
||||
@@ -720,7 +738,7 @@ func test_global_canvas_scale_never_drops_below_native_on_a_narrow_viewport() ->
|
||||
## (verified end-to-end, through act(), in test_atlas_agent_interface.gd;
|
||||
## this is the narrower unit-level check directly against the viewer).
|
||||
func test_jump_to_sets_held_rung_and_world_center() -> void:
|
||||
var v: StepCanvasViewer = auto_free(StepCanvasViewer.new())
|
||||
var v: StepCanvasViewer = _make_viewer()
|
||||
add_child(v)
|
||||
v.enter({"body_id": "GJ380c", "body_radius_km": 6238.4}, {})
|
||||
|
||||
@@ -733,7 +751,7 @@ func test_jump_to_sets_held_rung_and_world_center() -> void:
|
||||
## Omitting `rung` keeps whatever rung is currently held — the "revisit
|
||||
## within the same rung" common case shouldn't require repeating it.
|
||||
func test_jump_to_keeps_current_rung_when_omitted() -> void:
|
||||
var v: StepCanvasViewer = auto_free(StepCanvasViewer.new())
|
||||
var v: StepCanvasViewer = _make_viewer()
|
||||
add_child(v)
|
||||
v.enter({"body_id": "GJ380c", "body_radius_km": 6238.4}, {})
|
||||
v._scroll_rung(1, Vector2(400.0, 300.0)) # Region
|
||||
@@ -746,7 +764,7 @@ func test_jump_to_keeps_current_rung_when_omitted() -> void:
|
||||
## Jumping to Global always forces world_center to ZERO — Global has no
|
||||
## panned-center concept (mirrors _scroll_rung()'s own Global-rung handling).
|
||||
func test_jump_to_global_forces_world_center_to_zero() -> void:
|
||||
var v: StepCanvasViewer = auto_free(StepCanvasViewer.new())
|
||||
var v: StepCanvasViewer = _make_viewer()
|
||||
add_child(v)
|
||||
v.enter({"body_id": "GJ380c", "body_radius_km": 6238.4}, {})
|
||||
v._scroll_rung(1, Vector2(400.0, 300.0)) # Region
|
||||
@@ -757,7 +775,7 @@ func test_jump_to_global_forces_world_center_to_zero() -> void:
|
||||
|
||||
|
||||
func test_jump_to_unrecognized_rung_is_a_no_op() -> void:
|
||||
var v: StepCanvasViewer = auto_free(StepCanvasViewer.new())
|
||||
var v: StepCanvasViewer = _make_viewer()
|
||||
add_child(v)
|
||||
v.enter({"body_id": "GJ380c", "body_radius_km": 6238.4}, {})
|
||||
var rung_before: String = v.get_held_rung()
|
||||
@@ -770,7 +788,7 @@ func test_jump_to_unrecognized_rung_is_a_no_op() -> void:
|
||||
|
||||
|
||||
func test_get_current_canvas_summary_before_any_canvas_arrives() -> void:
|
||||
var v: StepCanvasViewer = auto_free(StepCanvasViewer.new())
|
||||
var v: StepCanvasViewer = _make_viewer()
|
||||
add_child(v)
|
||||
v.enter({"body_id": "GJ380c", "body_radius_km": 6238.4}, {})
|
||||
|
||||
@@ -786,7 +804,7 @@ func test_get_current_canvas_summary_before_any_canvas_arrives() -> void:
|
||||
## StepCanvasAnnotationLayer._draw_settlements()'s own dedup discipline —
|
||||
## covering the same cell id twice must not double-count).
|
||||
func test_get_current_canvas_summary_counts_match_a_fixture_canvas() -> void:
|
||||
var v: StepCanvasViewer = auto_free(StepCanvasViewer.new())
|
||||
var v: StepCanvasViewer = _make_viewer()
|
||||
add_child(v)
|
||||
v.enter({"body_id": "GJ380c", "body_radius_km": 6238.4}, {})
|
||||
v._scroll_rung(1, Vector2(400.0, 300.0)) # Region
|
||||
|
||||
@@ -150,6 +150,14 @@ var _legend_panel = null
|
||||
var _request = null # StepCanvasRequest
|
||||
var _disk_sweep_timer: Timer = null # T-1183 coarse background sweep trigger
|
||||
|
||||
## Test-injection-only (T-1193 first slice): threads into StepCanvasRequest's
|
||||
## disk_cache_root seam so tests can isolate the Tier-2/3 disk cache from the
|
||||
## machine-shared user://atlas_cache/ (one directory for every worktree gate,
|
||||
## live capture driver, and real play session — a warm shared cache delivers
|
||||
## canvases into tests written against a cold one). Set BEFORE add_child();
|
||||
## the request is constructed in _ready(). Production always leaves this "".
|
||||
var disk_cache_root_override: String = ""
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
anchor_right = 1.0
|
||||
@@ -176,7 +184,7 @@ func _ready() -> void:
|
||||
_annotation_layer.name = "AnnotationLayer"
|
||||
_canvas.add_child(_annotation_layer)
|
||||
|
||||
_request = StepCanvasRequest.new(self)
|
||||
_request = StepCanvasRequest.new(self, disk_cache_root_override)
|
||||
_request.name = "Request"
|
||||
add_child(_request)
|
||||
_request.canvas_ready.connect(_on_canvas_ready)
|
||||
|
||||
Reference in New Issue
Block a user