HelpImStuckInAFractal

World-space tutorial and narrative text

Date: 2026-10-27 Updated: 2026-10-28. User authorized implementation, including fractal artwork controls. Status: implementation authorized; builder choices below are explicitly identified.

Intent and confirmed requirements

Author an ordered list of editable text objects in each level, following the collectible editor’s conventions. Text has real thickness, including visible side surfaces. It is fixed in level-local coordinates and participates in ray intersections and depth occlusion with the fractal. The player can move around and through it. It is not a camera-facing billboard.

Each object has an imported font, centered multiline text, local position, uniform scale, Euler rotation, and a place-in-front-of-player action. Creation uses that same placement action. Placement matches the player’s scale and orientation and fits the text to 50% of the camera frustum. Expected content is at most 200 characters, usually three or four lines; extensive international typography is outside this first iteration.

Text reads left to right from either side. When the camera crosses its plane, the object performs a small fuzz animation while its lettering mirrors. Its authored transform does not change. The design must expose spatial text sampling for a future node editor that combines text with fractal fields; the node editor remains future work. Gradient-driven geometry distortion, field-normal tint, and escape-value volume carving are now in scope.

Start with a substantial headless testing harness and develop the feature against it. No windowed application launches are authorized by this work.

Proposed defaults, not additional user decisions

Representation and rendering decision

Use a cached two-dimensional signed-distance field for each laid-out block, extruded into a bounded three-dimensional field. A sample takes a point in text-local space and returns a signed distance estimate in those same units. Combine the 2D distance and the depth slab using the standard extrusion distance construction; preserve interior sign, counters such as the hole in O, front/back faces, and side walls. A rectangular mask alone is insufficient.

Godot’s TextServer provides glyph outlines, metrics, and shaping. Prefer a CPU bake from outlines, with curve-flattening error and texture discretization accounted for explicitly. Keep font access and baking behind an adapter. The first implementation gate must prove that a real imported TTF and OTF can be laid out and baked without a renderer. Do not quietly replace this with a GPU-only font rasterization path. Official API reference: https://docs.godotengine.org/en/4.5/classes/class_textserver.html#class-textserver-method-font-get-glyph-contours

The cached field is approximate. Record texel-to-local scale, signed-distance range, padding, and an error bound. Use conservative step sizes and a hit refinement policy that cannot step over a thin stroke. Stop sampling outside the field’s supported domain; clamp-to-edge texture sampling must not create infinite strips. Bake resolution, memory budget, and maximum supported close-up magnification are measured implementation choices, not claims of infinite detail. If one block texture cannot retain fine strokes at reasonable cost, the builder may use tiled/glyph field storage behind the same sampling contract.

For the initial renderer, intersect each candidate ray with the oriented text bounds and march text only over that interval, clipped by the nearest known opaque scene hit and view/fog limits. Compare text, fractal, and collectible hits in a common ray-distance metric. A scene miss must still allow a text hit, including text outside the fractal’s bounding cube. Early outs in the current shader must be audited for this requirement.

Keep the text query in a separate shader include with an explicit contract: canonical signed-field sample; conservative bounds; ray hit distance, position, normal, object ID and material ID; and a separate view-correction transform. The bounded renderer is a consumer of that field, not its definition. A future field graph can query the canonical field and enlarge/replace bounds for warps. The current gradient warp also does not preserve a distance bound; its effect path uses bounded interval sampling and refinement, as specified below.

Separate mask/composition assessment

A mask or separate pass may be useful when many text bounds overlap the screen, or when text needs a different resolution. Start with bounds rejection and bounded text queries in the existing primary-ray shader: that reuses its ray and precise hit distances, without new full-screen targets or a second ray setup. A screen-space rectangle mask is only a candidate mask, not text depth.

Do not use the existing quantized lens-depth alpha as the occlusion oracle: it is an optical-effect encoding, not sufficiently precise geometry depth. Any future split pass must retain adequate depth precision, normals/IDs as needed, and resolve the nearest hit before depth-dependent finishing. Compare the same sparse/dense workloads before claiming the split pass is faster. Separate compositing alone cannot implement future CSG or warped field sampling.

The selected visible surface goes through existing fog/scattering and level finishing, then the existing grid/painterly/lens/phosphor paths as applicable, then UI. Text uses its own hit distance for fog and optical depth. Preserve existing transition and renderer-mode behavior rather than inventing a second independent effects pipeline.

Fractal artwork controls (confirmed 2026-10-28)

Each text entry exposes these controls in the owning shape’s level settings, in its text list. They are saved per text object and editable independently.

Builder choices and implementation contracts

The following are implementation choices, not additional user requirements. Default all effects off (distortion 0, tint blend 0, dissolve disabled), source Owning shape, resample offset/rotation zero, resample scale 1, tint scale 1. Use explicit dissolve enable plus threshold so the old appearance is exact.

Preserve the selected source’s authored frame, relative to the owning shape; apply a virtual resample transform in the owner’s frame about the source’s origin. Increasing resample scale makes the borrowed pattern larger. Evaluate samples with the inverse transform. Tint’s independent scale further scales its sample coordinates about that origin and does not affect distortion or carving. Field coordinates stay anchored to the shape when text mirrors.

Stable shape identities, not list indices or labels, back explicit references. Legacy shapes receive IDs on load; reordering/renaming preserves references. A deleted source disables its dependent effects with a visible warning and retains the unresolved ID for repair. Owning shape is a dynamic sentinel that continues to mean the new owner when duplicated. Non-active source shapes may supply a field without becoming visible geometry.

Implement distortion as bounded inverse-domain displacement along a regularized DE gradient. Express its maximum displacement in text-local em units so bounds can be expanded conservatively. The gradient is sampled in the source frame and transformed back into text coordinates. Exact regularization and sampling resolution are builder choices that must be recorded with validation; never claim a warped field remains a conservative distance estimate without a bound.

Expose a production escape-field sampler for each supported fractal family. The threshold is an orbit escape scalar, not the distance estimator or the level-transition escape sphere. Pin the scalar’s formula, iteration count, bailout and non-escaping behavior in tests and UI help. Different families need not share numerically equivalent thresholds. Unsupported sources must report that fact instead of silently substituting surface distance. Avoid dependence on moving/still quality budgets: an artwork mask must not change as quality settles. Choose finite defaults/ranges from real-family fixtures.

The composed text solid is warped text intersected with the region whose escape scalar is at or below threshold. The renderer must find interior cut boundaries as well as original glyph faces. A scalar escape residual is not a signed distance: use bounded sampling/refinement or a proven step bound, and state the supported feature resolution. Preserve canonical base text queries for future field graph consumers; expose the composed solid separately.

Appearance, resampling and source-parameter edits invalidate rendering without rebaking the font. Bound and cache derived data where necessary, report its resolution and memory limits, and never silently omit excess text objects.

Mirror correction and fuzz transition

Define the canonical front as local +Z, text right as +X, and up as +Y. Determine view side from camera position in text-local coordinates, not camera look direction. On the back, reflect the sampled X coordinate about the block center. This corrects both glyph shapes and their order without reversing stored text or changing authored transforms. Transform normals with the same reflection.

With thick asymmetric letters, reflection necessarily changes the visible silhouette and the intersections with nearby fractal geometry. That is part of this view correction. The canonical signed field remains camera-independent so future field combinations can choose their own correction semantics.

Proposed fuzz duration: 180 ms. From 0–90 ms, increase a stable object-local noise dissolve until the text is fully hidden. At 90 ms, change the sampling handedness. From 90–180 ms, reveal the corrected text. Use noisy coverage without displacing bounds or adding rays. At full visibility the noise path is bypassed. Use stable object-ID seeding and elapsed time, not frame count.

Use a side hysteresis half-width of 0.02 em in text-local distance to suppress side-on chatter. On first visibility/load, initialize directly to the current side with no animation. During a running transition, retain a target side; before midpoint it may be updated without resetting the timer. After midpoint, finish the committed reveal and then transition again only if the camera is beyond the opposite threshold. No unbounded restart loop. Hidden/inactive objects reset transient state when next activated. None of this state is saved.

Dissolved fragments are absent from visible-depth selection: geometry behind must appear with its own depth and finishing, without invisible text occlusion. Apply the coverage decision consistently to the object’s first solid candidate for a ray; do not make front-face fuzz accidentally expose an opaque back face instead. Keep the geometric field and hit query usable independently of this effect.

Authoring and persistence

Add a text section alongside collectibles: add, remove, up/down reorder, collapsible entries, font file selector, multiline TextEdit, XYZ position, XYZ Euler angles in degrees, one positive uniform scale, and the placement button. Use the project’s existing rotation convention. Connect edits by stable object identity, not a stale captured list index. Editing text must consume movement shortcuts and Return must insert a newline.

Reject nonfinite transforms and nonpositive scale with a visible validation message. Support editing up to 200 characters; reject excess input clearly without silently truncating existing or loaded data. Four lines is a workload, not a hard line limit. Unsupported glyphs receive a visible replacement and warning. Invalid loaded entries remain recoverable and are not rendered.

Import font bytes, not absolute machine paths. Proposed portable storage: one workspace-level dictionary of content-hashed font blobs, base64-encoded in the saved JSON, referenced by object font IDs. Bundle the default font in the project. Identical bytes are stored once; duplicate filenames with distinct bytes stay distinct. A disk cache is disposable and regenerates from the saved font data. Exported saves carry the same font payload without editor-only imports. Apply explicit font payload size/resource limits before parsing.

Persist stable IDs, order, text, font ID, local transform, and versioned field settings needed for reproducibility. New saves round-trip without depending on an external file; old saves load with empty text lists. Include text/font state in level cloning and workspace load/reset paths. Preserve loaded unknown data where possible, report invalid fonts, and show a deterministic fallback.

Cache layout/field by font content hash, text, and bake settings. Transform, camera, ordering, mirror transition, and appearance changes must not rebake fonts or fields. Text and font edits invalidate exactly the affected cache entries. Coalesce live typing work; ignore stale results after another edit, deletion, or level load. Bound and release cache resources.

Harness architecture and staged acceptance

Reuse tests/test_case.gd for deterministic CPU, persistence, and UI tests. Add a focused headless runner which fails on exceptions, parse errors, timeouts, missing required capabilities, and missing expected test summaries. It must run both Godot tests and shader execution tests. Temporary test assets and save pointers are isolated from the user’s levels and font cache.

Reuse the approach in tests/material_shader_render_test.py: compile the production shader include and its actual integration path in headless Chromium, read floating-point pixels, and compare hit/miss, distance, normal, ID, and selected composed pixels with independent expected results. A copied CPU implementation is not proof that production shader code works. Synthetic boxes, rings, thin strokes, and analytic planes provide independent oracles; an actual font fixture adds end-to-end coverage without relying on font-specific OS rendering. Pin fixtures and licensing alongside the tests.

First milestone is a tested harness with analytic fixtures, runner failure injection, and the real-font headless feasibility gate. Contract test cases for unfinished product layers remain explicitly pending, not falsely passing. Enable each required test as its layer is implemented. The feature is complete only when all required cases pass and pending count is zero. Intentionally break representative depth, mirror, placement, and runner behavior in isolated fixtures to demonstrate that the tests detect those failures.

Godot headless tests do not prove its final GL driver rendering. Chromium execution checks shader math and pixels, but does not certify Godot viewport composition or macOS driver equivalence. Test the host wiring headlessly and report that remaining visual verification gap. No GPU performance claims from software-rendered Chromium timing.

Concrete headless regression cases

  1. Runner integrity: a deliberately failing assertion, parse error, missing executable, timeout, and absent summary each cause nonzero exit; a complete known-good fixture passes. Required capability absence never becomes green.
  2. Font feasibility: imported TTF and OTF yield metrics and a nonempty field for ABO; the O counter remains outside the solid, without a window.
  3. Portable fonts: save/load after removing access to the original font still produces the same layout and field hash; identical bytes deduplicate, and different bytes under the same filename remain independent.
  4. Font failures: missing, corrupt, oversized, and unsupported font inputs cause deterministic validation/fallback, without crashing or losing text.
  5. Multiline layout: unequal line lengths, kerning pairs, blank lines, leading/trailing spaces, and explicit returns obey centering and spacing.
  6. Content boundaries: empty/whitespace-only text has no solid; 200 characters over four lines work; excess input is reported without silent data loss.
  7. Solid geometry: analytic extrusions produce expected signed samples and front, back, side, and corner ray hits with correct distances and normals.
  8. Holes and thin strokes: rays through counters/gaps miss; thin-stroke and grazing rays hit within the declared bake error and march tolerance.
  9. Bounds safety: parallel rays, boundary rays, rays starting inside solids, and rays leaving a block are finite and terminate; no phantom edge strips.
  10. No billboard: orbiting/translating/rolling the camera never changes authored transform; oblique views retain the expected foreshortening.
  11. Both sides readable: asymmetric F R 12 fixtures have correct handedness and order from front/back while maintaining the same authored frame.
  12. Side detection: rotating the camera in place cannot trigger mirroring; crossing local Z thresholds can, even in a rotated/transformed level.
  13. Fuzz timeline: injected times at start, midpoint, and completion verify increasing breakup, zero coverage at the flip, and a fully restored result.
  14. Fuzz stability: side-on jitter and repeated crossings do not continually restart animation; 30/60/144 Hz step sequences reach the same timed state.
  15. Fuzz depth: discarded text coverage reveals the correct farther surface and its depth; no invisible occluder or unintended opaque rear glyph face.
  16. Level transform: translating, rotating, and resizing the parent level moves text predictably; local/world transforms round-trip at tested scales.
  17. Placement fit: wide/tall/multiline text occupies exactly 50% on the limiting viewport dimension within 0.5% tolerance, without clipping the other axis, across FOVs/aspects, camera roll, and thick bounds.
  18. Placement scale: changing player scale changes world size and distance proportionally but retains screen occupancy; large world positions and tiny local scales survive camera-relative packing without early float loss.
  19. Placement lifecycle: creation and button placement agree; later edits and camera/FOV changes do not refit; placement in a wall remains authored as requested and is occluded normally.
  20. Depth ordering: text before/behind/intersecting an analytic fractal surface selects the nearest hit per pixel; object reorder does not change it.
  21. Other scene hits: overlapping text and collectible spheres resolve by depth with deterministic near-equal ties, independent of list order.
  22. Fractal misses: text renders when the fractal misses, when outside its bounds, and against sky; genuine view/fog limits still apply.
  23. Effects integration: chosen text depth reaches the optical-depth path; surface finishing uses text distance; transition fades and renderer modes preserve text, while UI remains above it and navigation scans stay unchanged.
  24. Field independence: canonical sample results do not change with camera or fuzz phase; analytic min/max/subtraction consumers can query it without the text renderer. This tests the extension contract, not a node editor.
  25. Persistence: save/load retains font, text/newlines, ID, order, transform; legacy saves get no text; malformed values are recoverable; mirror/fuzz runtime state never leaks into saved data.
  26. Editor lifecycle: actual add/remove/reorder/edit controls affect only the intended object after list rebuilds, level switches, duplication, and deletion; typing and Return do not move the player or submit another UI.
  27. Cache behavior: camera motion, transforms, reorder, and fuzz produce zero layout/bake work; text/font changes invalidate only affected content; stale edit completions cannot resurrect removed objects or old levels.
  28. Work budgets: empty scenes perform no text samples; missed bounds and wholly occluded bounds perform no field samples; bounded march counts, allocations, bake counts, and texture memory are reported for 1/16/64 objects. Real performance timing is reported separately from these checks.

  29. Geometry distortion: zero is identity; nonzero deformation changes silhouettes and holes, maintains finite normals, and stays inside expanded bounds at extreme supported strengths and resample scales.
  30. Field tint: independent scale/blend, axis gradient alpha/strength/blend modes, and material frames agree with the production material pipeline; changing tint does not change geometry or the dissolve mask.
  31. Volume escape carve: analytic and real-family escape fixtures exercise threshold equality, non-escaping points, complete removal, interior cuts, farther scene visibility, and moving/still mask stability.
  32. Source resampling: translated/rotated/scaled owners and sources preserve expected sample positions; identity resampling matches the owner’s field; changing resampling never changes source parameters or text transforms.
  33. Source lifecycle: reorder, rename, duplicate, remove, save/load and inactive sources preserve IDs or show recoverable missing-source warnings.
  34. Combined effects: distortion, tint, carve, mirror and fuzz compose with correct depth; zero/disabled effects skip source samples; source edits refresh stills without rebaking font fields.

Performance costs to report during implementation

The implementation costs and supported limits are recorded below; this section remains the checklist for later renderer changes.

Delivery and scope

After this design is approved, write the implementation plan with the harness as its first milestone, then implement in the user’s existing checkout. Existing edits are present in main, collectible UI, shader tests, and other files; preserve them and do not include unrelated staged work in commits. If concurrent changes actually collide with implementation, follow the user’s isolation rule. Commit only logical task-owned changes. Do not push, publish, merge, rebase, cherry-pick, revert, or force-delete any directory.

Implemented choices and limits (2026-10-28)

Verification record

python3 tests/run_world_text.py is the focused acceptance entry point. It rejects nonzero exits, script/shader errors, missing/zero-check summaries, missing executables and timeouts. It runs data/UI, font, placement, runtime, 1/16/64-object budgets, integration, real-font fixture generation, production text shader execution and the existing material shader regression.

The production shader tests include analytic solid/counter fixtures, clipped rays, source transforms, mirror/fuzz, distortion, volume carving, tint sampling, real fractal-family samples, and the full fragment with a real baked font. Headless preview: build/tests/world-text/preview.png (generated, not a save).

The repository-wide headless run also exposed eight test-file failures outside the focused feature: build_type, default_level, fixed_frame_rate, fractal_params, mcp_bridge, new_file, trailer_mode and ui_journey. These concern missing/default save fixtures, resolution expectations, catalogue ordering and UI lifecycle. The new_file residual is a pre-existing fast-resolution float32 roundtrip (0.1 vs 0.10000000149); its new stable-ID expectation was updated separately. New text uniform expectations in shader/noise_view tests were updated and both pass. No claim is made that the entire current checkout passes its test suite.

No windowed native Godot rendering was run. Software WebGL validates production shader math/composition and produces the preview; it does not certify native macOS/Windows GPU visuals, driver compilation latency or performance.