Added Ridgeline Plots and layers output

This commit is contained in:
2026-05-29 17:17:06 -04:00
parent 56c59a1f9c
commit e38f11f71a
79 changed files with 356982 additions and 21 deletions

2
.serena/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
/cache
/project.local.yml

154
.serena/project.yml Normal file
View File

@@ -0,0 +1,154 @@
# the name by which the project can be referenced within Serena
project_name: "bubblechamber"
# list of languages for which language servers are started; choose from:
# al bash clojure cpp csharp
# csharp_omnisharp dart elixir elm erlang
# fortran fsharp go groovy haskell
# haxe java julia kotlin lua
# markdown
# matlab nix pascal perl php
# php_phpactor powershell python python_jedi r
# rego ruby ruby_solargraph rust scala
# swift terraform toml typescript typescript_vts
# vue yaml zig
# (This list may be outdated. For the current list, see values of Language enum here:
# https://github.com/oraios/serena/blob/main/src/solidlsp/ls_config.py
# For some languages, there are alternative language servers, e.g. csharp_omnisharp, ruby_solargraph.)
# Note:
# - For C, use cpp
# - For JavaScript, use typescript
# - For Free Pascal/Lazarus, use pascal
# Special requirements:
# Some languages require additional setup/installations.
# See here for details: https://oraios.github.io/serena/01-about/020_programming-languages.html#language-servers
# When using multiple languages, the first language server that supports a given file will be used for that file.
# The first language is the default language and the respective language server will be used as a fallback.
# Note that when using the JetBrains backend, language servers are not used and this list is correspondingly ignored.
languages:
- typescript
# the encoding used by text files in the project
# For a list of possible encodings, see https://docs.python.org/3.11/library/codecs.html#standard-encodings
encoding: "utf-8"
# line ending convention to use when writing source files.
# Possible values: unset (use global setting), "lf", "crlf", or "native" (platform default)
# This does not affect Serena's own files (e.g. memories and configuration files), which always use native line endings.
line_ending:
# The language backend to use for this project.
# If not set, the global setting from serena_config.yml is used.
# Valid values: LSP, JetBrains
# Note: the backend is fixed at startup. If a project with a different backend
# is activated post-init, an error will be returned.
language_backend:
# whether to use project's .gitignore files to ignore files
ignore_all_files_in_gitignore: true
# advanced configuration option allowing to configure language server-specific options.
# Maps the language key to the options.
# Have a look at the docstring of the constructors of the LS implementations within solidlsp (e.g., for C# or PHP) to see which options are available.
# No documentation on options means no options are available.
ls_specific_settings: {}
# list of additional paths to ignore in this project.
# Same syntax as gitignore, so you can use * and **.
# Note: global ignored_paths from serena_config.yml are also applied additively.
ignored_paths: []
# whether the project is in read-only mode
# If set to true, all editing tools will be disabled and attempts to use them will result in an error
# Added on 2025-04-18
read_only: false
# list of tool names to exclude.
# This extends the existing exclusions (e.g. from the global configuration)
#
# Below is the complete list of tools for convenience.
# To make sure you have the latest list of tools, and to view their descriptions,
# execute `uv run scripts/print_tool_overview.py`.
#
# * `activate_project`: Activates a project based on the project name or path.
# * `check_onboarding_performed`: Checks whether project onboarding was already performed.
# * `create_text_file`: Creates/overwrites a file in the project directory.
# * `delete_memory`: Delete a memory file. Should only happen if a user asks for it explicitly,
# for example by saying that the information retrieved from a memory file is no longer correct
# or no longer relevant for the project.
# * `edit_memory`: Replaces content matching a regular expression in a memory.
# * `execute_shell_command`: Executes a shell command.
# * `find_file`: Finds files in the given relative paths
# * `find_referencing_symbols`: Finds symbols that reference the given symbol using the language server backend
# * `find_symbol`: Performs a global (or local) search using the language server backend.
# * `get_current_config`: Prints the current configuration of the agent, including the active and available projects, tools, contexts, and modes.
# * `get_symbols_overview`: Gets an overview of the top-level symbols defined in a given file.
# * `initial_instructions`: Provides instructions Serena usage (i.e. the 'Serena Instructions Manual')
# for clients that do not read the initial instructions when the MCP server is connected.
# * `insert_after_symbol`: Inserts content after the end of the definition of a given symbol.
# * `insert_before_symbol`: Inserts content before the beginning of the definition of a given symbol.
# * `list_dir`: Lists files and directories in the given directory (optionally with recursion).
# * `list_memories`: List available memories. Any memory can be read using the `read_memory` tool.
# * `onboarding`: Performs onboarding (identifying the project structure and essential tasks, e.g. for testing or building).
# * `read_file`: Reads a file within the project directory.
# * `read_memory`: Read the content of a memory file. This tool should only be used if the information
# is relevant to the current task. You can infer whether the information
# is relevant from the memory file name.
# You should not read the same memory file multiple times in the same conversation.
# * `rename_memory`: Renames or moves a memory. Moving between project and global scope is supported
# (e.g., renaming "global/foo" to "bar" moves it from global to project scope).
# * `rename_symbol`: Renames a symbol throughout the codebase using language server refactoring capabilities.
# For JB, we use a separate tool.
# * `replace_content`: Replaces content in a file (optionally using regular expressions).
# * `replace_symbol_body`: Replaces the full definition of a symbol using the language server backend.
# * `safe_delete_symbol`:
# * `search_for_pattern`: Performs a search for a pattern in the project.
# * `write_memory`: Write some information (utf-8-encoded) about this project that can be useful for future tasks to a memory in md format.
# The memory name should be meaningful.
excluded_tools: []
# list of tools to include that would otherwise be disabled (particularly optional tools that are disabled by default).
# This extends the existing inclusions (e.g. from the global configuration).
included_optional_tools: []
# fixed set of tools to use as the base tool set (if non-empty), replacing Serena's default set of tools.
# This cannot be combined with non-empty excluded_tools or included_optional_tools.
fixed_tools: []
# list of mode names to that are always to be included in the set of active modes
# The full set of modes to be activated is base_modes + default_modes.
# If the setting is undefined, the base_modes from the global configuration (serena_config.yml) apply.
# Otherwise, this setting overrides the global configuration.
# Set this to [] to disable base modes for this project.
# Set this to a list of mode names to always include the respective modes for this project.
base_modes:
# list of mode names that are to be activated by default.
# The full set of modes to be activated is base_modes + default_modes.
# If the setting is undefined, the default_modes from the global configuration (serena_config.yml) apply.
# Otherwise, this overrides the setting from the global configuration (serena_config.yml).
# This setting can, in turn, be overridden by CLI parameters (--mode).
default_modes:
# initial prompt for the project. It will always be given to the LLM upon activating the project
# (contrary to the memories, which are loaded on demand).
initial_prompt: ""
# time budget (seconds) per tool call for the retrieval of additional symbol information
# such as docstrings or parameter information.
# This overrides the corresponding setting in the global configuration; see the documentation there.
# If null or missing, use the setting from the global configuration.
symbol_info_budget:
# list of regex patterns which, when matched, mark a memory entry as readonly.
# Extends the list from the global configuration, merging the two lists.
read_only_memory_patterns: []
# list of regex patterns for memories to completely ignore.
# Matching memories will not appear in list_memories or activate_project output
# and cannot be accessed via read_memory or write_memory.
# To access ignored memory files, use the read_file tool on the raw file path.
# Extends the list from the global configuration, merging the two lists.
# Example: ["_archive/.*", "_episodes/.*"]
ignored_memory_patterns: []

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.5 MiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.7 MiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 3.5 MiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 12 MiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 10 MiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.7 MiB

View File

@@ -0,0 +1,15 @@
<!DOCTYPE html><html><head><meta charset="utf-8"><title>Layered piece · film + QFT carpet + bubble chamber</title>
<style>body{margin:0;background:#0b0b0b;color:#bbb;font:12px/1.5 ui-monospace,Menlo,monospace;padding:26px;max-width:1900px}
h1{font-weight:400;letter-spacing:.22em;text-transform:uppercase;font-size:13px;color:#a5d4c9}
.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(460px,1fr));gap:20px;margin-top:18px}
figure{margin:0;background:#fff;border:1px solid #262626;overflow:hidden}img{width:100%;display:block}
figcaption{padding:8px 10px;color:#e8e4d8;background:#111}small{color:#777;display:block}
.notes{color:#999;background:#0e1816;padding:14px 18px;border-left:3px solid #a5d4c9;margin:18px 0}</style></head><body>
<h1>Layered piece — film · QFT vacuum carpet · bubble-chamber event</h1>
<div class="notes">back→front: cream ground · milky film/diffusion · 3 spaced QFT carpet sheets (back two blurred = air-gap depth of field) · bubble-chamber event (multiply) · film grain. The literal plexi stack, previewed as one image.</div>
<div class=grid><figure><img src="01_mono-calm-sea.svg"><figcaption>mono calm sea<small>LAMBDA-2648 · mono</small></figcaption></figure>
<figure><img src="02_magenta-over-teal.svg"><figcaption>magenta over teal<small>MESON-5113 · magentarise</small></figcaption></figure>
<figure><img src="03_quiet-vast.svg"><figcaption>quiet vast<small>NUCLEON-2131 · mono</small></figcaption></figure>
<figure><img src="04_kind-verdigris.svg"><figcaption>kind verdigris<small>HYPERON-8444 · kind</small></figcaption></figure>
<figure><img src="05_ember-warm.svg"><figcaption>ember warm<small>CASCADE-2755 · kindrise</small></figcaption></figure>
<figure><img src="06_seethe-bold.svg"><figcaption>seethe bold<small>MESON-5113 · magentarise</small></figcaption></figure></div></body></html>

3
output/layering/m.html Normal file
View File

@@ -0,0 +1,3 @@
<!DOCTYPE html><html><head><meta charset="utf-8">
<style>html,body{margin:0;background:#222}.grid{display:grid;grid-template-columns:repeat(2,1fr);gap:8px;padding:10px;width:2400px}figure{margin:0;position:relative;background:#fff;overflow:hidden}img{width:100%;display:block}figcaption{position:absolute;left:0;bottom:0;right:0;padding:6px 10px;font:14px ui-monospace,monospace;color:#fff;background:linear-gradient(transparent,#000d)}</style></head><body>
<div class="grid"><figure><img src="01_mono-calm-sea.svg"><figcaption>mono calm sea</figcaption></figure><figure><img src="02_magenta-over-teal.svg"><figcaption>magenta over teal</figcaption></figure><figure><img src="03_quiet-vast.svg"><figcaption>quiet vast</figcaption></figure><figure><img src="04_kind-verdigris.svg"><figcaption>kind verdigris</figcaption></figure><figure><img src="05_ember-warm.svg"><figcaption>ember warm</figcaption></figure><figure><img src="06_seethe-bold.svg"><figcaption>seethe bold</figcaption></figure></div></body></html>

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 4.1 MiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 11 MiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 9.5 MiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.0 MiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 11 MiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 4.3 MiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 12 MiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 2.3 MiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 2.2 MiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 7.0 MiB

View File

@@ -0,0 +1,23 @@
<!DOCTYPE html><html><head><meta charset="utf-8"><title>QFT × Bubble Chamber · sketch 01</title>
<style>body{margin:0;background:#0b0b0b;color:#bbb;font:12px/1.5 ui-monospace,Menlo,monospace;padding:26px;max-width:1900px}
h1{font-weight:400;letter-spacing:.22em;text-transform:uppercase;font-size:13px;color:#a5d4c9}
.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(460px,1fr));gap:20px;margin-top:18px}
figure{margin:0;background:#fff;border:1px solid #262626;overflow:hidden}img{width:100%;display:block}
figcaption{padding:8px 10px;color:#e8e4d8;background:#111}small{color:#777;display:block}
.notes{color:#999;background:#0e1816;padding:14px 18px;border-left:3px solid #a5d4c9;margin:18px 0}</style></head><body>
<h1>QFT × Bubble Chamber · sketch 01 — the field that authors the trace, under the trace</h1>
<div class="notes">Two independently-seeded plates stacked as one image: a QFT field plate UNDERNEATH a bubble-chamber plate,
joined with <code>mix-blend-mode</code> (multiply on light grounds = lightbox; screen on dark = luminous negative).
Furniture deduplicated — the bubble chamber is the studied, labelled top plate. Each frame chases one feeling.</div>
<div class=grid>
<figure><img src="01_lightbox-positive.svg"><figcaption>lightbox positive<small>VACUUM-5113 × LAMBDA-2648 · multiply</small></figcaption></figure>
<figure><img src="02_negative-void.svg"><figcaption>negative void<small>GAUGE-2046 × HYPERON-8444 · screen</small></figcaption></figure>
<figure><img src="03_cyanotype-communion.svg"><figcaption>cyanotype communion<small>PROPAGATOR-2755 × CASCADE-2755 · screen</small></figcaption></figure>
<figure><img src="04_field-as-atmosphere.svg"><figcaption>field as atmosphere<small>FEYNMAN-7167 × NUCLEON-2131 · multiply</small></figcaption></figure>
<figure><img src="05_scale-rhyme-nautilus.svg"><figcaption>scale rhyme nautilus<small>LATTICE-1003 × HYPERON-8444 · multiply</small></figcaption></figure>
<figure><img src="06_sepia-archive.svg"><figcaption>sepia archive<small>VACUUM-5113 × LAMBDA-2648 · multiply</small></figcaption></figure>
<figure><img src="07_earned-colour-kind.svg"><figcaption>earned colour kind<small>GAUGE-2046 × HYPERON-8444 · multiply</small></figcaption></figure>
<figure><img src="08_chladni-quiet.svg"><figcaption>chladni quiet<small>FEYNMAN-7167 × NUCLEON-2131 · multiply</small></figcaption></figure>
<figure><img src="09_dense-lattice-rare-event.svg"><figcaption>dense lattice rare event<small>LATTICE-1003 × NUCLEON-2131 · multiply</small></figcaption></figure>
<figure><img src="10_vertigo-tesseract.svg"><figcaption>vertigo tesseract<small>PROPAGATOR-2755 × HYPERON-8444 · multiply</small></figcaption></figure>
</div></body></html>

View File

@@ -0,0 +1,14 @@
<!DOCTYPE html><html><head><meta charset="utf-8">
<style>html,body{margin:0;background:#222}.grid{display:grid;grid-template-columns:repeat(2,1fr);gap:8px;padding:10px;width:2400px}figure{margin:0;position:relative;background:#fff;overflow:hidden}img{width:100%;display:block}figcaption{position:absolute;left:0;bottom:0;right:0;padding:6px 10px;font:14px ui-monospace,monospace;color:#fff;background:linear-gradient(transparent,#000d)}</style></head><body>
<div class="grid">
<figure><img src="01_lightbox-positive.svg"><figcaption>lightbox positive</figcaption></figure>
<figure><img src="02_negative-void.svg"><figcaption>negative void</figcaption></figure>
<figure><img src="03_cyanotype-communion.svg"><figcaption>cyanotype communion</figcaption></figure>
<figure><img src="04_field-as-atmosphere.svg"><figcaption>field as atmosphere</figcaption></figure>
<figure><img src="05_scale-rhyme-nautilus.svg"><figcaption>scale rhyme nautilus</figcaption></figure>
<figure><img src="06_sepia-archive.svg"><figcaption>sepia archive</figcaption></figure>
<figure><img src="07_earned-colour-kind.svg"><figcaption>earned colour kind</figcaption></figure>
<figure><img src="08_chladni-quiet.svg"><figcaption>chladni quiet</figcaption></figure>
<figure><img src="09_dense-lattice-rare-event.svg"><figcaption>dense lattice rare event</figcaption></figure>
<figure><img src="10_vertigo-tesseract.svg"><figcaption>vertigo tesseract</figcaption></figure>
</div></body></html>

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 4.9 MiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 4.7 MiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 4.9 MiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 4.3 MiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 4.7 MiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 4.9 MiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 4.2 MiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 4.9 MiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 4.7 MiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.0 MiB

View File

@@ -0,0 +1,23 @@
<!DOCTYPE html><html><head><meta charset="utf-8"><title>QFT × Bubble Chamber · sketch 02 · magenta-cream</title>
<style>body{margin:0;background:#0b0b0b;color:#bbb;font:12px/1.5 ui-monospace,Menlo,monospace;padding:26px;max-width:1900px}
h1{font-weight:400;letter-spacing:.22em;text-transform:uppercase;font-size:13px;color:#d6a5c4}
.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(460px,1fr));gap:20px;margin-top:18px}
figure{margin:0;background:#fff;border:1px solid #262626;overflow:hidden}img{width:100%;display:block}
figcaption{padding:8px 10px;color:#e8e4d8;background:#111}small{color:#777;display:block}
.notes{color:#999;background:#180e16;padding:14px 18px;border-left:3px solid #d6a5c4;margin:18px 0}</style></head><body>
<h1>QFT × Bubble Chamber · sketch 02 — thin field between cream & the magenta event</h1>
<div class="notes">Built on <b>78_final-lively-magenta-cream</b> (MESON-5113 · magentarise · cream). A thin-line but
present QFT lattice is slipped between the cream ground and the bubble chamber (multiply on top). The event is constant;
each frame varies the QFT geometry and a hue family chosen to converse with the magenta tracks + burnt-orange disk.</div>
<div class=grid>
<figure><img src="01_tonal-magenta-echo.svg"><figcaption>tonal magenta echo<small>field: FEYNMAN-7167</small></figcaption></figure>
<figure><img src="02_magenta-teal-duotone.svg"><figcaption>magenta teal duotone<small>field: GAUGE-2046</small></figcaption></figure>
<figure><img src="03_ember-gold-underglow.svg"><figcaption>ember gold underglow<small>field: LATTICE-1003</small></figcaption></figure>
<figure><img src="04_violet-indigo-recede.svg"><figcaption>violet indigo recede<small>field: PROPAGATOR-2755</small></figcaption></figure>
<figure><img src="05_verdigris-chladni.svg"><figcaption>verdigris chladni<small>field: VACUUM-5113</small></figcaption></figure>
<figure><img src="06_teal-lattice-gold-links.svg"><figcaption>teal lattice gold links<small>field: FEYNMAN-7167</small></figcaption></figure>
<figure><img src="07_rose-ghost-quiet.svg"><figcaption>rose ghost quiet<small>field: GAUGE-2046</small></figcaption></figure>
<figure><img src="08_pale-spectral-lattice.svg"><figcaption>pale spectral lattice<small>field: LATTICE-1003</small></figcaption></figure>
<figure><img src="09_cyan-blueprint-ripples.svg"><figcaption>cyan blueprint ripples<small>field: PROPAGATOR-2755</small></figcaption></figure>
<figure><img src="10_trace-family-communion.svg"><figcaption>trace family communion<small>field: FEYNMAN-7167</small></figcaption></figure>
</div></body></html>

View File

@@ -0,0 +1,14 @@
<!DOCTYPE html><html><head><meta charset="utf-8">
<style>html,body{margin:0;background:#222}.grid{display:grid;grid-template-columns:repeat(2,1fr);gap:8px;padding:10px;width:2400px}figure{margin:0;position:relative;background:#fff;overflow:hidden}img{width:100%;display:block}figcaption{position:absolute;left:0;bottom:0;right:0;padding:6px 10px;font:14px ui-monospace,monospace;color:#fff;background:linear-gradient(transparent,#000d)}</style></head><body>
<div class="grid">
<figure><img src="01_tonal-magenta-echo.svg"><figcaption>tonal magenta echo</figcaption></figure>
<figure><img src="02_magenta-teal-duotone.svg"><figcaption>magenta teal duotone</figcaption></figure>
<figure><img src="03_ember-gold-underglow.svg"><figcaption>ember gold underglow</figcaption></figure>
<figure><img src="04_violet-indigo-recede.svg"><figcaption>violet indigo recede</figcaption></figure>
<figure><img src="05_verdigris-chladni.svg"><figcaption>verdigris chladni</figcaption></figure>
<figure><img src="06_teal-lattice-gold-links.svg"><figcaption>teal lattice gold links</figcaption></figure>
<figure><img src="07_rose-ghost-quiet.svg"><figcaption>rose ghost quiet</figcaption></figure>
<figure><img src="08_pale-spectral-lattice.svg"><figcaption>pale spectral lattice</figcaption></figure>
<figure><img src="09_cyan-blueprint-ripples.svg"><figcaption>cyan blueprint ripples</figcaption></figure>
<figure><img src="10_trace-family-communion.svg"><figcaption>trace family communion</figcaption></figure>
</div></body></html>

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 832 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 946 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 1.3 MiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 568 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 1.1 MiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 909 KiB

View File

@@ -0,0 +1,3 @@
<!DOCTYPE html><html><head><meta charset="utf-8">
<style>html,body{margin:0;background:#222}.grid{display:grid;grid-template-columns:repeat(2,1fr);gap:8px;padding:10px;width:2000px}figure{margin:0;position:relative;background:#fff;overflow:hidden}img{width:100%;display:block}figcaption{position:absolute;left:0;bottom:0;right:0;padding:6px 10px;font:13px ui-monospace,monospace;color:#fff;background:linear-gradient(transparent,#000d)}</style></head><body>
<div class="grid"><figure><img src="01_calm-ridgeline.svg"><figcaption>calm ridgeline · calm · low-q swells, few blips</figcaption></figure><figure><img src="02_vacuum-seethe.svg"><figcaption>vacuum seethe · vacuum seethe · many spiralling blips</figcaption></figure><figure><img src="03_dense-fine.svg"><figcaption>dense fine · dense fine weave</figcaption></figure><figure><img src="04_sparse-bold.svg"><figcaption>sparse bold · sparse · bold soft swells</figcaption></figure><figure><img src="05_deep-horizon.svg"><figcaption>deep horizon · deep horizon · more sky above the mound</figcaption></figure><figure><img src="06_verdigris-seethe.svg"><figcaption>verdigris seethe · verdigris vacuum · oxidised</figcaption></figure></div></body></html>

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 451 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 452 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 452 KiB

View File

@@ -0,0 +1,4 @@
<!DOCTYPE html><html><head><meta charset="utf-8"><title>vacuum carpet · plexi deck</title>
<style>html,body{margin:0;background:#0c0c0c}.stage{position:relative;width:1500px;height:1500px;margin:30px auto;background:rgb(226,219,199);box-shadow:0 0 120px #000 inset}.stage img{position:absolute;inset:0;width:100%;height:100%}.L3{filter:blur(2.2px);opacity:.5;transform:translateY(-6px) scale(1.01)}.L2{filter:blur(1px);opacity:.72;transform:translateY(-2px)}.L1{opacity:.95}.cap{max-width:1500px;margin:0 auto;color:#888;font:12px ui-monospace,monospace;padding:0 4px}</style></head><body>
<div class="stage"><img class="L3" src="layers/L3_back.svg"><img class="L2" src="layers/L2_mid.svg"><img class="L1" src="layers/L1_front.svg"></div>
<p class="cap">deck (back→front): film/diffusion · L3 · L2 · L1 · [bubble chamber]. back sheets blurred+dimmed = air-gap depth of field.</p></body></html>

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 3.2 MiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 2.9 MiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 10 MiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 14 MiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 7.5 MiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 17 MiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 2.7 MiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 4.9 MiB

View File

@@ -0,0 +1,12 @@
<!DOCTYPE html><html><head><meta charset="utf-8">
<style>html,body{margin:0;background:#222}.grid{display:grid;grid-template-columns:repeat(2,1fr);gap:8px;padding:10px;width:2000px}figure{margin:0;position:relative;background:#fff;overflow:hidden}img{width:100%;display:block}figcaption{position:absolute;left:0;bottom:0;right:0;padding:6px 10px;font:13px ui-monospace,monospace;color:#fff;background:linear-gradient(transparent,#000d)}</style></head><body>
<div class="grid">
<figure><img src="01_floor-to-horizon.svg"><figcaption>floor to horizon · flat floor · low pitch · rushes to a horizon</figcaption></figure>
<figure><img src="02_floor-three-quarter.svg"><figcaption>floor three quarter · floor · 3/4 view · vanishing to the side</figcaption></figure>
<figure><img src="03_tunnel-inside.svg"><figcaption>tunnel inside · camera INSIDE · walls/floor/ceiling rush inward</figcaption></figure>
<figure><img src="04_corridor-3q.svg"><figcaption>corridor 3q · corridor · slight angle · one-point-ish</figcaption></figure>
<figure><img src="05_exaggerated-bleed.svg"><figcaption>exaggerated bleed · full lattice · strong persp · near corner blows off-page</figcaption></figure>
<figure><img src="06_diagonal-rush.svg"><figcaption>diagonal rush · diagonal rush across the frame</figcaption></figure>
<figure><img src="07_vaulted-ceiling.svg"><figcaption>vaulted ceiling · looking UP · ceiling lattice vaulting away</figcaption></figure>
<figure><img src="08_deep-floor-strong.svg"><figcaption>deep floor strong · widest, deepest floor · strongest vanishing</figcaption></figure>
</div></body></html>

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 252 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 253 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 252 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 252 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 253 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 254 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 252 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 254 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 253 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 252 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 256 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 255 KiB

View File

@@ -0,0 +1,16 @@
<!DOCTYPE html><html><head><meta charset="utf-8">
<style>html,body{margin:0;background:#222}.grid{display:grid;grid-template-columns:repeat(3,1fr);gap:8px;padding:10px;width:2100px}figure{margin:0;position:relative;background:#fff;overflow:hidden}img{width:100%;display:block}figcaption{position:absolute;left:0;bottom:0;right:0;padding:6px 10px;font:13px ui-monospace,monospace;color:#fff;background:linear-gradient(transparent,#000d)}</style></head><body>
<div class="grid">
<figure><img src="01_iso-default.svg"><figcaption>iso default · isometric · default 3/4</figcaption></figure>
<figure><img src="02_yaw-front.svg"><figcaption>yaw front · yaw 0° · facing a face</figcaption></figure>
<figure><img src="03_yaw-deep.svg"><figcaption>yaw deep · yaw -70° · spun round</figcaption></figure>
<figure><img src="04_pitch-low.svg"><figcaption>pitch low · pitch 12° · near eye-level</figcaption></figure>
<figure><img src="05_pitch-steep.svg"><figcaption>pitch steep · pitch 58° · looking down</figcaption></figure>
<figure><img src="06_pitch-top.svg"><figcaption>pitch top · pitch 78° · near top-down</figcaption></figure>
<figure><img src="07_persp-mild.svg"><figcaption>persp mild · perspective 0.45 · gentle depth</figcaption></figure>
<figure><img src="08_persp-strong.svg"><figcaption>persp strong · perspective 0.9 · dist 2.6 · dramatic</figcaption></figure>
<figure><img src="09_persp-corner.svg"><figcaption>persp corner · into a corner · 1-pt-ish</figcaption></figure>
<figure><img src="10_roll-cant.svg"><figcaption>roll cant · roll 18° · canted + persp 0.4</figcaption></figure>
<figure><img src="11_hero-tunnel.svg"><figcaption>hero tunnel · low + strong persp · tunnel</figcaption></figure>
<figure><img src="12_hero-vault.svg"><figcaption>hero vault · steep + persp · vaulted ceiling</figcaption></figure>
</div></body></html>

136
src/qft/carpet.js Normal file
View File

@@ -0,0 +1,136 @@
/* ============================================================
qft/carpet.js — the VACUUM CARPET: a ridgeline / joyplot field.
Rows of low-frequency field waves + soft-edged sinusoidal "blips"
(Gaussian-windowed wave-packets) that drift and rotate phase across
rows, so localized excitations SPIRAL through the depth of the
stack. Perspective-compressed to a horizon → an infinite carpet of
quantum fluctuations. Rows are rendered as smooth Catmull-Rom
curves (no triangular peaks).
Two modes:
solid opaque paper + hidden-line occlusion (single-sheet joyplot art)
plate transparent, strokes only (for stacking on spaced plexi sheets)
============================================================ */
import { makeRng, range, chance } from '../rng.js';
import { resolveSubstrate } from './palette.js';
function hslToRgb(h, s, l) {
h = ((h % 1) + 1) % 1;
const a = s * Math.min(l, 1 - l);
const f = (n) => { const k = (n + h * 12) % 12; return l - a * Math.max(-1, Math.min(k - 3, 9 - k, 1)); };
return [Math.round(f(0) * 255), Math.round(f(8) * 255), Math.round(f(4) * 255)];
}
const css = (c) => `rgb(${c[0]},${c[1]},${c[2]})`;
// Catmull-Rom → cubic-bezier path: smooth curve through the points.
function smoothPath(pts) {
if (pts.length < 3) return 'M ' + pts.map(p => `${p.x.toFixed(1)} ${p.y.toFixed(1)}`).join(' L ');
let d = `M ${pts[0].x.toFixed(1)} ${pts[0].y.toFixed(1)} `;
for (let i = 0; i < pts.length - 1; i++) {
const p0 = pts[i - 1] || pts[i], p1 = pts[i], p2 = pts[i + 1], p3 = pts[i + 2] || p2;
const c1x = p1.x + (p2.x - p0.x) / 6, c1y = p1.y + (p2.y - p0.y) / 6;
const c2x = p2.x - (p3.x - p1.x) / 6, c2y = p2.y - (p3.y - p1.y) / 6;
d += `C ${c1x.toFixed(1)} ${c1y.toFixed(1)} ${c2x.toFixed(1)} ${c2y.toFixed(1)} ${p2.x.toFixed(1)} ${p2.y.toFixed(1)} `;
}
return d;
}
export function carpetSVG(size, opts = {}) {
const o = Object.assign({
seed: 'VACUUM-5113', salt: 'carpet', mode: 'solid', substrate: 'cream',
rows: 46, horizon: 0.34, wFar: 0.56, wNear: 0.68, overlap: 1.7, chaos: 0.5,
mound: 0.4, // 0 = flat band edge-to-edge · 1 = pronounced central mound
blips: 1.0, // density of the spiralling wave-packet excitations
hue: 0.52, hue2: 0.55, sat: 0.55, lightNear: 0.34, lightFar: 0.62,
strokeNear: 1.7, strokeFar: 0.5,
}, opts);
const W = size, H = size, u = size / 1000;
const paper = resolveSubstrate(o.substrate).paper.flat;
const rng = makeRng(o.seed, o.salt);
// ---- base sea: low-frequency field modes (phase drifts slowly per row) ----
const M = Math.round(4 + o.chaos * 8);
const modes = [];
for (let m = 0; m < M; m++) {
const f = range(rng, 0.4, 3.0 + o.chaos * 5); // low q: long swells dominate
modes.push({ f, a: 1 / (1 + f * 1.0), phi: range(rng, 0, Math.PI * 2), drift: range(rng, -1, 1) * (0.06 + f * 0.02) });
}
const norm = modes.reduce((s, m) => s + m.a, 0) || 1;
// ---- coherent excitations: soft wave-packets that drift + rotate phase
// across rows, so they SPIRAL through the depth of the stack ----
const nExc = Math.round((2 + o.chaos * 5) * o.blips);
const exc = [];
for (let e = 0; e < nExc; e++) {
exc.push({
x0: range(rng, 0.12, 0.88),
row0: range(rng, 0, o.rows - 1),
span: range(rng, 0.12, 0.3) * o.rows, // rows over which it lives
w: range(rng, 0.05, 0.11), // packet width (soft edge)
k: range(rng, 3.5, 7), // oscillations within the packet
amp: range(rng, 0.30, 0.7),
drift: range(rng, -0.018, 0.018), // lateral drift per row
phase: range(rng, 0, Math.PI * 2),
phaseAdv: range(rng, -0.45, 0.45), // phase rotation per row → spiral
sign: rng() < 0.5 ? -1 : 1,
});
}
const value = (t, r) => {
let s = 0;
for (const m of modes) s += m.a * Math.sin(2 * Math.PI * m.f * t + m.phi + r * m.drift);
s /= norm;
let blip = 0;
for (const e of exc) {
const dr = r - e.row0;
const env = Math.exp(-Math.pow(dr / (e.span * 0.5), 2));
if (env < 0.02) continue;
const cx = e.x0 + e.drift * dr;
const g = Math.exp(-Math.pow((t - cx) / e.w, 2)); // soft Gaussian window
blip += e.sign * e.amp * env * g * Math.cos((t - cx) / e.w * e.k + e.phase + e.phaseAdv * dr);
}
return s + blip * (0.5 + 0.5 * o.chaos);
};
// vertical placement: rows bunch at the horizon, spread to the front
const hY = o.horizon * H, bottom = H * 0.99;
const baseY = [];
for (let r = 0; r < o.rows; r++) baseY.push(hY + (bottom - hY) * Math.pow(r / (o.rows - 1), 1.7));
const transparent = o.mode === 'plate';
const eMin = 1 - 0.55 * o.mound;
const env = (t) => eMin + (1 - eMin) * Math.exp(-Math.pow((t - 0.5) / 0.42, 2));
let body = '';
for (let r = 0; r < o.rows; r++) {
const d = r / (o.rows - 1);
const half = (o.wFar + (o.wNear - o.wFar) * d) * W; // ≥ ~0.56W → bleeds off both edges
const cx = W / 2;
const localGap = r > 0 ? baseY[r] - baseY[r - 1] : (baseY[1] - baseY[0]);
const amp = Math.max(2 * u, o.overlap * localGap);
const npts = Math.max(56, Math.round(half / (2.6 * u)));
const pts = [];
for (let i = 0; i <= npts; i++) {
const t = i / npts;
const e = env(t);
pts.push({ x: cx - half + t * 2 * half, y: baseY[r] - amp * e * value(t, r) - amp * 0.55 * (e - eMin) * d });
}
const path = smoothPath(pts);
const hue = o.hue + (o.hue2 - o.hue) * (1 - d);
const light = o.lightFar + (o.lightNear - o.lightFar) * d;
const sat = o.sat * (0.6 + 0.4 * d);
const col = css(hslToRgb(hue, sat, light));
const sw = (o.strokeFar + (o.strokeNear - o.strokeFar) * d) * u;
if (!transparent) {
const fill = `${path} L ${(cx + half).toFixed(1)} ${bottom.toFixed(1)} L ${(cx - half).toFixed(1)} ${bottom.toFixed(1)} Z`;
body += `<path d="${fill}" fill="${css(paper)}" stroke="none"/>\n`;
}
const op = transparent ? (0.45 + 0.5 * d).toFixed(2) : 1;
body += `<path d="${path}" fill="none" stroke="${col}" stroke-width="${sw.toFixed(2)}" stroke-opacity="${op}" stroke-linecap="round" stroke-linejoin="round"/>\n`;
}
const bg = transparent ? '' : `<rect width="${W}" height="${H}" fill="${css(paper)}"/>`;
return `<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="${W}" height="${H}" viewBox="0 0 ${W} ${H}">
${bg}
<g>${body}</g>
</svg>`;
}

View File

@@ -32,6 +32,12 @@ export function paramsFromSeed(seed) {
cubicScale: r(0.92, 1.05), cubicRot: r(-0.25, 0.25),
cubicOriginX: 0, cubicOriginY: 0,
cubicN: 1, // half-range of the cubic lattice. 1=3³=27 verts/~54 edges (default); 2=5³=125 verts/~300 edges (denser); 3=7³=343 verts (heaviest).
// ---- cubic CAMERA: move the viewpoint of the cartesian grid ----
cubicYaw: -0.7854, cubicPitch: 0.6155, cubicRoll: 0, // default = classic isometric three-quarter
cubicPersp: 0, // 0 = isometric/orthographic; 1 = normal; up to 2 = exaggerated
cubicDist: 3.4, // camera distance (depth units); smaller = more dramatic foreshortening
cubicZShift: 0, // push camera INTO the grid (>0) for the "extends to infinity" look
cubicNx: null, cubicNy: null, cubicNz: null, // per-axis half-ranges (null → cubicN). wide+deep+shallow = infinite floor
photonCyclesPerUnit: 12, // wave frequency along photon edges; higher = finer ripples
schlegelScale: r(0.88, 1.02), schlegelRot: r(-0.30, 0.30),
schlegelOriginX: 0, schlegelOriginY: 0,

View File

@@ -36,7 +36,12 @@ function translateVerts(verts, dx, dy) {
export function generateQFTScene(params) {
// ---- cubic ---- (cubicN controls density: 1=27v, 2=125v, 3=343v)
const cubic = buildCubic(Math.max(1, params.cubicN | 0));
// camera (yaw/pitch/roll/persp/dist) moves the viewpoint of the cartesian grid
const cubic = buildCubic(Math.max(1, params.cubicN | 0), 1.0, {
yaw: params.cubicYaw, pitch: params.cubicPitch, roll: params.cubicRoll,
persp: params.cubicPersp, dist: params.cubicDist, zShift: params.cubicZShift,
nx: params.cubicNx, ny: params.cubicNy, nz: params.cubicNz,
});
rotateScale(cubic.vertices, params.cubicRot, params.cubicScale);
translateVerts(cubic.vertices, params.cubicOriginX, params.cubicOriginY);
cubic.id = 'cubic'; cubic.propagator = 'photon';

View File

@@ -11,33 +11,74 @@
simplified for legibility without literal E8 fidelity)
============================================================ */
/* 3D cubic lattice, isometric-projected to 2D and centred at origin.
/* 3D cubic lattice with a movable CAMERA, projected to 2D and centred at origin.
N is half-range: vertices at integer (i,j,k) ∈ [-N..N]^3.
N=1 → 3×3×3 = 27 vertices, ~54 edges. Plenty at thumbnail scale. */
export function buildCubic(N = 1, scale = 1.0) {
const vs = [], v3to1 = new Map();
// standard isometric, centred: x = (i-k)*cos30, y = (i+k)*sin30 - j
N=1 → 3×3×3 = 27 vertices, ~54 edges. Plenty at thumbnail scale.
opts (all optional) move the viewpoint of the cartesian grid:
yaw rotation about the vertical (Y) axis — spin left/right
pitch rotation about the horizontal (X) axis — tip toward/away
roll rotation about the view (Z) axis — cant
persp 0 = orthographic/isometric · 1 = normal · up to 2 = exaggerated
dist camera distance (in depth units); smaller = more dramatic
zShift push the lattice along the view axis — positive drives the CAMERA
INTO the grid so near cells blow off-page and far cells rush to a
vanishing point (the "extends to infinity" look)
nx,ny,nz per-axis half-ranges. Make a wide, deep, SHALLOW slab
(nx,nz big, ny 01) for an infinite FLOOR to the horizon rather
than a closed cube. Fall back to N when unset.
Defaults reproduce a classic isometric three-quarter view (persp 0). */
export function buildCubic(N = 1, scale = 1.0, opts = {}) {
const yaw = opts.yaw ?? -0.7854; // -45°
const pitch = opts.pitch ?? 0.6155; // ~35.26° → isometric
const roll = opts.roll ?? 0;
const persp = Math.max(0, Math.min(2, opts.persp ?? 0));
const nx = Math.max(1, Math.round(opts.nx ?? N));
const ny = Math.max(0, Math.round(opts.ny ?? N)); // 0 → a single flat layer (floor)
const nz = Math.max(1, Math.round(opts.nz ?? N));
const dist = (opts.dist ?? 3.4) * Math.max(1, nz);
const zShift = opts.zShift ?? 0;
const nearClip = 0.12 * dist; // cull cells at/behind the camera plane
const cy = Math.cos(yaw), sy = Math.sin(yaw);
const cx = Math.cos(pitch), sx = Math.sin(pitch);
const cz = Math.cos(roll), sz = Math.sin(roll);
const vs = [], clipped = [], v3to1 = new Map();
const proj = (i, j, k) => {
const ix = (i - k) * 0.866;
const iy = (i + k) * 0.5 - j;
// normalise so the cube fits within ~[-1,1] at scale=1
return { x: ix * scale * 0.5, y: iy * scale * 0.5 };
// raw lattice coords; j is the vertical (up) axis
let x = i, y = j, z = k;
let x1 = x * cy + z * sy; // yaw about Y
let z1 = -x * sy + z * cy;
let y1 = y;
let y2 = y1 * cx - z1 * sx; // pitch about X
let z2 = y1 * sx + z1 * cx;
let x2 = x1;
let xr = x2 * cz - y2 * sz; // roll about the view axis
let yr = x2 * sz + y2 * cz;
let zr = z2 + zShift;
const denom = dist - zr * persp; // camera on +z, looking toward -z
const isClip = persp > 0 && denom <= nearClip;
const f = persp > 0 ? dist / Math.max(denom, nearClip) : 1;
return { p: { x: xr * f * scale * 0.5, y: -yr * f * scale * 0.5 }, isClip };
};
for (let i = -N; i <= N; i++)
for (let j = -N; j <= N; j++)
for (let k = -N; k <= N; k++) {
for (let i = -nx; i <= nx; i++)
for (let j = -ny; j <= ny; j++)
for (let k = -nz; k <= nz; k++) {
v3to1.set(`${i},${j},${k}`, vs.length);
vs.push(proj(i, j, k));
const r = proj(i, j, k);
vs.push(r.p); clipped.push(r.isClip);
}
// edges along each axis (i / j / k adjacency)
// edges along each axis (i / j / k adjacency); skip any crossing the near plane
const es = [];
for (let i = -N; i <= N; i++)
for (let j = -N; j <= N; j++)
for (let k = -N; k <= N; k++) {
const link = (a, b) => { if (!clipped[a] && !clipped[b]) es.push({ a, b }); };
for (let i = -nx; i <= nx; i++)
for (let j = -ny; j <= ny; j++)
for (let k = -nz; k <= nz; k++) {
const a = v3to1.get(`${i},${j},${k}`);
if (i < N) es.push({ a, b: v3to1.get(`${i + 1},${j},${k}`) });
if (j < N) es.push({ a, b: v3to1.get(`${i},${j + 1},${k}`) });
if (k < N) es.push({ a, b: v3to1.get(`${i},${j},${k + 1}`) });
if (i < nx) link(a, v3to1.get(`${i + 1},${j},${k}`));
if (j < ny) link(a, v3to1.get(`${i},${j + 1},${k}`));
if (k < nz) link(a, v3to1.get(`${i},${j},${k + 1}`));
}
return { vertices: vs, edges: es };
}

40
tools/archive-wall.html Normal file
View File

@@ -0,0 +1,40 @@
<!DOCTYPE html><html><head><meta charset="utf-8">
<title>The Archive — typology wall mock</title>
<style>
html,body{margin:0;background:#16140f;color:#cabfa6}
/* warm gallery wall, even hang, consistent frames + museum labels */
body{padding:64px 56px 80px;font:13px/1.5 ui-monospace,"SFMono-Regular",Menlo,monospace}
.head{letter-spacing:.34em;text-transform:uppercase;font-size:13px;color:#9fb7af;margin:0 0 4px}
.sub{color:#6f6a5c;font-size:12px;margin:0 0 40px;letter-spacing:.06em}
.wall{display:grid;grid-template-columns:repeat(4,1fr);gap:44px 40px}
figure{margin:0}
.mat{background:#f3ecdb;padding:16px;box-shadow:0 2px 0 #0008,0 18px 40px -18px #000c;border:1px solid #000}
.mat img{display:block;width:100%;aspect-ratio:1/1;object-fit:cover;background:#fff}
figcaption{margin-top:11px;display:flex;justify-content:space-between;gap:10px;align-items:baseline}
.cat{color:#9fb7af;letter-spacing:.12em}
.ttl{color:#cabfa6;flex:1;text-align:center}
.kind{color:#6f6a5c;text-transform:uppercase;letter-spacing:.1em;font-size:11px}
</style></head><body>
<p class="head">Traces of the Invisible · a catalogue</p>
<p class="sub">one fabricated archive · evidence (bubble chamber) · cause (field) · synthesis · A3 plates, seeds as catalogue numbers</p>
<div class="wall" id=w></div>
<script>
const items=[
["output/iterations-claude-craft/78_final-lively-magenta-cream.svg","BC·001","Lively magenta event","evidence"],
["output/qft/sketch07/02_single-huge-nautilus.svg","QF·011","Single E8 nautilus","cause"],
["output/iterations-claude-craft/03_boron-bluered-cream.svg","BC·014","Boron · blue/red","evidence"],
["output/qft/sketch05/10_concentric-rosettes-pale-rose.svg","QF·024","Concentric rosettes","cause"],
["output/qft-bc/sketch01/05_scale-rhyme-nautilus.svg","SY·002","Scale rhyme · nautilus","synthesis"],
["output/iterations-claude-craft/22_kind-warm-relic.svg","BC·022","Kind · warm relic","evidence"],
["output/qft/sketch04/06_bone-radial-gradient.svg","QF·046","Bone radial field","cause"],
["output/iterations-claude-craft/26_kind-selenium-soft.svg","BC·026","Kind · selenium","evidence"],
["output/qft/sketch05/02_dense-radial-cream.svg","QF·052","Dense radial lattice","cause"],
["output/iterations-claude-craft/10_boron-gold-olive.svg","BC·010","Boron · gold/olive","evidence"],
["output/qft-bc/sketch02/10_trace-family-communion.svg","SY·010","Communion","synthesis"],
["output/iterations-claude-craft/16_warm-halo-nocturne.svg","BC·016","Warm halo nocturne","evidence"],
];
w.innerHTML=items.map(([src,cat,ttl,kind])=>
`<figure><div class="mat"><img src="../${src}"></div>
<figcaption><span class="cat">${cat}</span><span class="ttl">${ttl}</span></figcaption>
<div style="text-align:right" class="kind">${kind}</div></figure>`).join("");
</script></body></html>

140
tools/layering.mjs Normal file
View File

@@ -0,0 +1,140 @@
/* ============================================================
layering.mjs — the assembled piece: FILM/diffusion (back) + a QFT
vacuum-carpet DECK (middle, 3 spaced plate sheets) + a BUBBLE-CHAMBER
event (front), composited as one image (and the literal plexi stack).
Produces variations into output/layering/.
Usage: node tools/layering.mjs [size]
============================================================ */
import { writeFileSync, mkdirSync } from 'node:fs';
import { carpetSVG } from '../src/qft/carpet.js';
import { generateScene } from '../src/scene/scene.js';
import { renderSVG } from '../src/render/svgVector.js';
import { paramsFromSeed as bcParams } from '../src/scene/params.js';
import { GROUPS, TOGGLES, FIXED } from '../src/ui/controls.js';
const SIZE = +(process.argv[2] || 1500);
const OUT = 'output/layering';
mkdirSync(OUT, { recursive: true });
const u = SIZE / 1000;
const dataUri = (svg) => 'data:image/svg+xml;base64,' + Buffer.from(svg).toString('base64');
// ---- FILM / diffusion: milky clouds (light, soft) the light diffuses through ----
function filmSVG(o = {}) {
const { seed = 7, freq = 0.0016, octaves = 4, tone = [236, 228, 208], density = 0.55 } = o;
const t = tone.map(v => (v / 255).toFixed(3));
return `<svg xmlns="http://www.w3.org/2000/svg" width="${SIZE}" height="${SIZE}" viewBox="0 0 ${SIZE} ${SIZE}">
<defs><filter id="fog" x="0" y="0" width="100%" height="100%">
<feTurbulence type="fractalNoise" baseFrequency="${freq}" numOctaves="${octaves}" seed="${seed}" stitchTiles="stitch" result="n"/>
<feColorMatrix in="n" type="matrix" values="0 0 0 0 ${t[0]} 0 0 0 0 ${t[1]} 0 0 0 0 ${t[2]} 0 0 0 ${density} 0"/>
</filter></defs><rect width="${SIZE}" height="${SIZE}" filter="url(#fog)"/></svg>`;
}
// ---- film GRAIN veil (fine, dark, low opacity) on top ----
function grainSVG(o = {}) {
const { seed = 19, tone = [38, 32, 26], amount = 0.5 } = o;
const t = tone.map(v => (v / 255).toFixed(3));
return `<svg xmlns="http://www.w3.org/2000/svg" width="${SIZE}" height="${SIZE}" viewBox="0 0 ${SIZE} ${SIZE}">
<defs><filter id="g" x="0" y="0" width="100%" height="100%">
<feTurbulence type="fractalNoise" baseFrequency="0.9" numOctaves="2" seed="${seed}" stitchTiles="stitch" result="n"/>
<feColorMatrix in="n" type="matrix" values="0 0 0 0 ${t[0]} 0 0 0 0 ${t[1]} 0 0 0 0 ${t[2]} 0 0 0 ${amount} 0"/>
</filter></defs><rect width="${SIZE}" height="${SIZE}" filter="url(#g)"/></svg>`;
}
// ---- bubble-chamber event ----
function bcSVG(seed, over = {}) {
const p = { ...FIXED, ...bcParams(seed) };
for (const g of GROUPS) for (const c of g.controls) if (!(c.id in p)) p[c.id] = c.value;
for (const t of TOGGLES) if (!(t.id in p)) p[t.id] = t.value;
p.invert = true; p.showHeader = false;
Object.assign(p, over);
return renderSVG(generateScene(p), p, SIZE);
}
// build a 3-sheet carpet deck for a variation (hue gradient + chaos rising to front)
function deck(c) {
const base = { mode: 'plate', rows: 46, horizon: c.horizon ?? 0.37, wFar: 0.58, wNear: 0.7,
overlap: c.overlap ?? 1.7, mound: c.mound ?? 0.35, sat: c.sat ?? 0.58, lightNear: 0.33, lightFar: 0.56, blips: c.blips ?? 1.0 };
const lerp = (a, b, t) => a + (b - a) * t;
return [0, 1, 2].map(i => {
const t = i / 2; // 0 back → 1 front
return carpetSVG(SIZE, { ...base, salt: 'field' + i,
hue: lerp(c.hueBack, c.hueFront, t), hue2: lerp(c.hueBack, c.hueFront, t) + 0.035,
chaos: lerp((c.chaos ?? 0.6) * 0.8, c.chaos ?? 0.6, t) });
});
}
function compose(v) {
const film = dataUri(filmSVG(v.film));
const sheets = deck(v.carpet).map(dataUri);
const bc = dataUri(bcSVG(v.bcSeed, v.bcOver));
const grain = dataUri(grainSVG(v.grain));
const base = v.base || 'rgb(226,219,199)';
const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${SIZE}" height="${SIZE}" viewBox="0 0 ${SIZE} ${SIZE}">
<defs>
<filter id="b3" x="-5%" y="-5%" width="110%" height="110%"><feGaussianBlur stdDeviation="${(2.4 * u).toFixed(2)}"/></filter>
<filter id="b2" x="-5%" y="-5%" width="110%" height="110%"><feGaussianBlur stdDeviation="${(1.1 * u).toFixed(2)}"/></filter>
</defs>
<rect width="${SIZE}" height="${SIZE}" fill="${base}"/>
<image x="0" y="0" width="${SIZE}" height="${SIZE}" href="${film}" opacity="0.6"/>
<image x="0" y="0" width="${SIZE}" height="${SIZE}" href="${sheets[0]}" filter="url(#b3)" opacity="0.5"/>
<image x="0" y="0" width="${SIZE}" height="${SIZE}" href="${sheets[1]}" filter="url(#b2)" opacity="0.72"/>
<image x="0" y="0" width="${SIZE}" height="${SIZE}" href="${sheets[2]}" opacity="0.95"/>
<image x="0" y="0" width="${SIZE}" height="${SIZE}" href="${bc}" style="mix-blend-mode:multiply"/>
<image x="0" y="0" width="${SIZE}" height="${SIZE}" href="${grain}" opacity="${v.grainOpacity ?? 0.45}" style="mix-blend-mode:multiply"/>
</svg>`;
writeFileSync(`${OUT}/${v.name}.svg`, svg);
console.log(` ${v.name} (bc=${v.bcSeed})`);
}
// ============================================================
// Variations — film + carpet + event, ranging mood & "loudness".
// Guiding thesis: calm sea, loud event.
// ============================================================
const VARIATIONS = [
{ name: '01_mono-calm-sea', base: 'rgb(228,221,201)',
film: { seed: 3, density: 0.5, tone: [236, 228, 208] }, grain: { amount: 0.4 },
carpet: { hueBack: 0.58, hueFront: 0.50, chaos: 0.35, blips: 0.7, mound: 0.32 },
bcSeed: 'LAMBDA-2648', bcOver: { palette: 'mono' } },
{ name: '02_magenta-over-teal', base: 'rgb(228,221,201)',
film: { seed: 8, density: 0.5 }, grain: { amount: 0.4 },
carpet: { hueBack: 0.54, hueFront: 0.47, chaos: 0.45, blips: 1.0, mound: 0.35 },
bcSeed: 'MESON-5113', bcOver: { palette: 'magentarise', saturation: 1.05 } },
{ name: '03_quiet-vast', base: 'rgb(230,224,206)',
film: { seed: 12, density: 0.42, tone: [238, 231, 212] }, grain: { amount: 0.32 },
carpet: { hueBack: 0.57, hueFront: 0.52, chaos: 0.22, blips: 0.5, mound: 0.28, overlap: 1.5 },
bcSeed: 'NUCLEON-2131', bcOver: { palette: 'mono', primaries: 7, burst: 0.4, cosmics: 2, deltaRate: 0.45 } },
{ name: '04_kind-verdigris', base: 'rgb(226,221,205)',
film: { seed: 21, density: 0.5 }, grain: { amount: 0.4 },
carpet: { hueBack: 0.45, hueFront: 0.40, chaos: 0.5, blips: 1.1, sat: 0.42, mound: 0.35 },
bcSeed: 'HYPERON-8444', bcOver: { palette: 'kind', saturation: 1.0 } },
{ name: '05_ember-warm', base: 'rgb(230,222,202)',
film: { seed: 30, density: 0.52, tone: [238, 226, 204] }, grain: { amount: 0.42 },
carpet: { hueBack: 0.10, hueFront: 0.07, chaos: 0.5, blips: 1.0, sat: 0.5, mound: 0.34 },
bcSeed: 'CASCADE-2755', bcOver: { palette: 'kindrise', saturation: 1.0 } },
{ name: '06_seethe-bold', base: 'rgb(227,220,200)',
film: { seed: 41, density: 0.55 }, grain: { amount: 0.46 },
carpet: { hueBack: 0.55, hueFront: 0.47, chaos: 0.8, blips: 1.4, mound: 0.4, overlap: 1.9 },
bcSeed: 'MESON-5113', bcOver: { palette: 'magentarise', saturation: 1.08 } },
];
console.log(`Layered piece — ${VARIATIONS.length} variations → ${OUT}/`);
for (const v of VARIATIONS) compose(v);
const cap = (v) => v.name.replace(/^\d+_/, '').replace(/-/g, ' ');
writeFileSync(`${OUT}/index.html`, `<!DOCTYPE html><html><head><meta charset="utf-8"><title>Layered piece · film + QFT carpet + bubble chamber</title>
<style>body{margin:0;background:#0b0b0b;color:#bbb;font:12px/1.5 ui-monospace,Menlo,monospace;padding:26px;max-width:1900px}
h1{font-weight:400;letter-spacing:.22em;text-transform:uppercase;font-size:13px;color:#a5d4c9}
.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(460px,1fr));gap:20px;margin-top:18px}
figure{margin:0;background:#fff;border:1px solid #262626;overflow:hidden}img{width:100%;display:block}
figcaption{padding:8px 10px;color:#e8e4d8;background:#111}small{color:#777;display:block}
.notes{color:#999;background:#0e1816;padding:14px 18px;border-left:3px solid #a5d4c9;margin:18px 0}</style></head><body>
<h1>Layered piece — film · QFT vacuum carpet · bubble-chamber event</h1>
<div class="notes">back→front: cream ground · milky film/diffusion · 3 spaced QFT carpet sheets (back two blurred = air-gap depth of field) · bubble-chamber event (multiply) · film grain. The literal plexi stack, previewed as one image.</div>
<div class=grid>${VARIATIONS.map(v => `<figure><img src="${v.name}.svg"><figcaption>${cap(v)}<small>${v.bcSeed} · ${v.bcOver?.palette || 'mono'}</small></figcaption></figure>`).join('\n')}</div></body></html>`);
writeFileSync(`${OUT}/m.html`, `<!DOCTYPE html><html><head><meta charset="utf-8">
<style>html,body{margin:0;background:#222}.grid{display:grid;grid-template-columns:repeat(2,1fr);gap:8px;padding:10px;width:2400px}figure{margin:0;position:relative;background:#fff;overflow:hidden}img{width:100%;display:block}figcaption{position:absolute;left:0;bottom:0;right:0;padding:6px 10px;font:14px ui-monospace,monospace;color:#fff;background:linear-gradient(transparent,#000d)}</style></head><body>
<div class="grid">${VARIATIONS.map(v => `<figure><img src="${v.name}.svg"><figcaption>${cap(v)}</figcaption></figure>`).join('')}</div></body></html>`);
console.log(`contact sheets -> ${OUT}/index.html , m.html`);

View File

@@ -0,0 +1,59 @@
/* ============================================================
qft-bc-composite.mjs — quick play: a QFT field plate UNDERNEATH
a bubble-chamber plate. Both render to standalone SVG; we embed
each as a self-contained data-URI <image> in one outer SVG (no
defs/id collisions), and put the bubble chamber on top with
mix-blend-mode:multiply — the lightbox metaphor: the field glows
underneath, the dark ink darkens it, the light paper drops out.
Usage:
node tools/qft-bc-composite.mjs [qftSeed] [bcSeed] [out.html] [size]
============================================================ */
import { writeFileSync } from 'node:fs';
// --- QFT side ---
import { generateQFTScene } from '../src/qft/scene.js';
import { paramsFromSeed as qftParams } from '../src/qft/params.js';
import { renderQFTSVG } from '../src/qft/renderer.js';
// --- bubble chamber side ---
import { generateScene } from '../src/scene/scene.js';
import { renderSVG } from '../src/render/svgVector.js';
import { paramsFromSeed as bcParams } from '../src/scene/params.js';
import { GROUPS, TOGGLES, FIXED } from '../src/ui/controls.js';
const argv = process.argv.slice(2);
const qftSeed = argv[0] || 'VACUUM-5113';
const bcSeed = argv[1] || 'LAMBDA-2648';
const out = argv[2] || '/tmp/qft-bc.html';
const SIZE = +(argv[3] || 1600);
// pale QFT substrate so it reads as a luminous ground under the ink
const qp = qftParams(qftSeed);
qp.substrate = 'cream'; // force a light ground so the multiply'd BC ink reads
qp.glow = 0.6;
// bubble chamber: default mono, light cream paper → multiply lets the field through
const bp = { ...FIXED, ...bcParams(bcSeed) };
for (const g of GROUPS) for (const c of g.controls) if (!(c.id in bp)) bp[c.id] = c.value;
for (const t of TOGGLES) if (!(t.id in bp)) bp[t.id] = t.value;
const qftSvg = renderQFTSVG(generateQFTScene(qp), qp, SIZE);
const bcSvg = renderSVG(generateScene(bp), bp, SIZE);
const dataUri = (svg) => 'data:image/svg+xml;base64,' + Buffer.from(svg).toString('base64');
const composite =
`<svg xmlns="http://www.w3.org/2000/svg" width="${SIZE}" height="${SIZE}" viewBox="0 0 ${SIZE} ${SIZE}">
<image x="0" y="0" width="${SIZE}" height="${SIZE}" href="${dataUri(qftSvg)}"/>
<image x="0" y="0" width="${SIZE}" height="${SIZE}" href="${dataUri(bcSvg)}" style="mix-blend-mode:multiply"/>
</svg>`;
const html =
`<!doctype html><meta charset="utf-8">
<style>html,body{margin:0;background:#222}img,svg{display:block}</style>
${composite}`;
writeFileSync(out, html);
writeFileSync(out.replace(/\.html$/, '.svg'), composite);
console.log(`composite -> ${out} (qft=${qftSeed} ${qp.archetype}, bc=${bcSeed}, ${SIZE}px)`);

177
tools/qft-bc-magenta.mjs Normal file
View File

@@ -0,0 +1,177 @@
/* ============================================================
qft-bc-magenta.mjs — sketch 02 of the JOINED series.
Built on 78_final-lively-magenta-cream (seed MESON-5113,
palette magentarise, cream paper). A THIN-LINE but present
QFT field is slipped BETWEEN the cream ground and the bubble
chamber: cream paper → thin QFT lattice → magentarise event
(multiply on top). The bubble-chamber layer is constant; each
frame varies the QFT geometry AND its hue family, chosen to
converse with the magenta tracks + burnt-orange disk + cream.
Usage: node tools/qft-bc-magenta.mjs [size]
============================================================ */
import { writeFileSync, mkdirSync } from 'node:fs';
import { generateQFTScene } from '../src/qft/scene.js';
import { paramsFromSeed as qftParams } from '../src/qft/params.js';
import { renderQFTSVG } from '../src/qft/renderer.js';
import { generateScene } from '../src/scene/scene.js';
import { renderSVG } from '../src/render/svgVector.js';
import { paramsFromSeed as bcParams } from '../src/scene/params.js';
import { GROUPS, TOGGLES, FIXED } from '../src/ui/controls.js';
const SIZE = +(process.argv[2] || 1700);
const OUT = 'output/qft-bc/sketch02';
mkdirSync(OUT, { recursive: true });
// F(hueStart,hueEnd,sat,light,opacity[,stroke]) — thin strokes throughout.
const F = (h0, h1, s, l, o, st = 0.6) => ({ hueStart: h0, hueEnd: h1, saturation: s, lightness: l, opacity: o, stroke: st });
const OFF = F(0, 0, 0, 0.5, 0);
const paper = (flat, gi = [10, 9, 8], go = [-16, -16, -16]) => ({
flat, glowIn: [flat[0] + gi[0], flat[1] + gi[1], flat[2] + gi[2]], glowOut: [flat[0] + go[0], flat[1] + go[1], flat[2] + go[2]],
});
const W = (x, y, amplitude, sigma) => ({ x, y, amplitude, sigma });
const VX = (x, y, strength, sigma) => ({ x, y, strength, sigma });
const SW = (kx, ky, amplitude, phase = 0) => ({ kx, ky, amplitude, phase });
const R = (x, y, count = 6, r0 = 0.08, dR = 0.10, propagator = 'photon') => ({ x, y, count, r0, dR, propagator });
const CREAM = [236, 228, 208]; // pale cream so the BC's own paper carries the tone
// thin-line QFT base: light vignette/glow so the ground stays flat under the BC.
const QBASE = {
substrate: 'cream', paperOverride: paper(CREAM), vignOverride: [120, 110, 90],
showHeader: false, glow: 0.18, vign: 0.10, stroke: 0.7, segmentsPerEdge: 8,
photonCyclesPerUnit: 12, linkCurvature: 0.18,
};
// constant bubble-chamber layer — the 78 piece (lively magenta on cream).
function bcLayer() {
const p = { ...FIXED, ...bcParams('MESON-5113') };
for (const g of GROUPS) for (const c of g.controls) if (!(c.id in p)) p[c.id] = c.value;
for (const t of TOGGLES) if (!(t.id in p)) p[t.id] = t.value;
p.palette = 'magentarise'; p.paperTone = 'cream'; p.invert = true;
p.showHeader = false; p.saturation = 1.05; // a touch livelier
return p;
}
const dataUri = (svg) => 'data:image/svg+xml;base64,' + Buffer.from(svg).toString('base64');
function composite(v) {
const qp = { ...qftParams(v.qftSeed), ...QBASE, ...(v.qftOver || {}) };
const bp = bcLayer();
const qftSvg = renderQFTSVG(generateQFTScene(qp), qp, SIZE);
const bcSvg = renderSVG(generateScene(bp), bp, SIZE);
const s = v.qScale ?? 1, qw = SIZE * s, qh = SIZE * s;
const qx = (SIZE - qw) / 2 + (v.qDx || 0), qy = (SIZE - qh) / 2 + (v.qDy || 0);
const out =
`<svg xmlns="http://www.w3.org/2000/svg" width="${SIZE}" height="${SIZE}" viewBox="0 0 ${SIZE} ${SIZE}">
<rect width="${SIZE}" height="${SIZE}" fill="rgb(${CREAM.join(',')})"/>
<image x="${qx.toFixed(0)}" y="${qy.toFixed(0)}" width="${qw.toFixed(0)}" height="${qh.toFixed(0)}" href="${dataUri(qftSvg)}" opacity="${v.qOpacity ?? 1}"/>
<image x="0" y="0" width="${SIZE}" height="${SIZE}" href="${dataUri(bcSvg)}" style="mix-blend-mode:multiply"/>
</svg>`;
writeFileSync(`${OUT}/${v.name}.svg`, out);
console.log(` ${v.name} (qft=${v.qftSeed}/${qp.archetype})`);
return out;
}
// ============================================================
// 10 hue families × QFT geometries, all under the same magenta event.
// ============================================================
const VARIATIONS = [
// 1 — TONAL ECHO. Field in the same magenta family, paler & desaturated:
// the trace's colour, whispered, as its own substrate.
{ name: '01_tonal-magenta-echo', qftSeed: 'FEYNMAN-7167',
qftOver: { cubicN: 2, e8Count: 3, e8OriginRadius: 0.6, linkCount: 6,
fields: { cubic: F(0.86, 0.90, 0.46, 0.50, 0.60, 0.55), schlegel: F(0.80, 0.84, 0.42, 0.48, 0.52, 0.55),
e8: F(0.90, 0.94, 0.52, 0.50, 0.65, 0.6), ripple: OFF, links: F(0.92, 0.88, 0.58, 0.48, 0.66, 0.8) } } },
// 2 — THE COMPLEMENT. Teal/viridian — magenta's opposite — for maximum
// vibrance under the warm event. A swirling vortex lattice.
{ name: '02_magenta-teal-duotone', qftSeed: 'GAUGE-2046',
qftOver: { cubicN: 2, e8Count: 1, e8OriginRadius: 0.64, linkCount: 6, vortices: [VX(0, 0, 0.8, 0.3)],
fields: { cubic: F(0.47, 0.50, 0.55, 0.46, 0.58, 0.55), schlegel: F(0.50, 0.46, 0.50, 0.44, 0.50, 0.6),
e8: F(0.45, 0.48, 0.55, 0.48, 0.55, 0.6), ripple: OFF, links: F(0.48, 0.44, 0.60, 0.46, 0.62, 0.8) } } },
// 3 — EMBER UNDERGLOW. Gold→copper field, picking up the burnt-orange disk:
// the event smouldering on a bed of its own embers. Nautilus rosettes.
{ name: '03_ember-gold-underglow', qftSeed: 'LATTICE-1003',
qftOver: { cubicN: 2, e8Style: 'nautilus', e8Count: 3, nautilusTurns: 2.6, nautilusPerTurn: 14, nautilusGrowth: 0.2, linkCount: 6,
fields: { cubic: F(0.10, 0.08, 0.62, 0.46, 0.62, 0.55), schlegel: F(0.08, 0.06, 0.55, 0.44, 0.52, 0.55),
e8: F(0.12, 0.07, 0.72, 0.46, 0.70, 0.6), ripple: OFF, links: F(0.07, 0.05, 0.82, 0.44, 0.72, 0.8) } } },
// 4 — COOL RECEDE. Violet→indigo, the cool neighbour of magenta — the field
// steps back into shadow while the warm trace advances. Big tesseract.
{ name: '04_violet-indigo-recede', qftSeed: 'PROPAGATOR-2755',
qftOver: { cubicN: 1, schlegelScale: 1.3, schlegelOuterR: 0.86, e8Count: 2, linkCount: 5,
fields: { cubic: F(0.70, 0.74, 0.45, 0.50, 0.48, 0.5), schlegel: F(0.74, 0.78, 0.50, 0.46, 0.55, 0.6),
e8: F(0.68, 0.72, 0.45, 0.50, 0.50, 0.6), ripple: OFF, links: F(0.76, 0.72, 0.50, 0.48, 0.55, 0.8) } } },
// 5 — VERDIGRIS. Desaturated sea-green patina — aged copper plate — under a
// Chladni standing-wave membrane. Quiet, oxidised, archival.
{ name: '05_verdigris-chladni', qftSeed: 'VACUUM-5113',
qftOver: { cubicN: 2, e8Count: 0, linkCount: 3, standingWaves: [SW(6, 1, 0.04), SW(1, 6, 0.04)],
fields: { cubic: F(0.44, 0.48, 0.30, 0.54, 0.55, 0.55), schlegel: F(0.46, 0.50, 0.26, 0.52, 0.45, 0.55),
e8: OFF, ripple: OFF, links: F(0.42, 0.46, 0.35, 0.52, 0.50, 0.75) } } },
// 6 — SPLIT-COMPLEMENT. Teal lattice, GOLD propagator links (tied to the
// disk accent) — a two-colour field that brackets the magenta on both sides.
{ name: '06_teal-lattice-gold-links', qftSeed: 'FEYNMAN-7167',
qftOver: { cubicN: 2, e8Count: 3, e8OriginRadius: 0.58, linkCount: 9, linkCurvature: 0.3,
fields: { cubic: F(0.49, 0.52, 0.45, 0.50, 0.52, 0.55), schlegel: F(0.50, 0.47, 0.42, 0.48, 0.45, 0.55),
e8: F(0.11, 0.08, 0.55, 0.55, 0.55, 0.6), ripple: OFF, links: F(0.12, 0.07, 0.70, 0.55, 0.7, 0.85) } } },
// 7 — ROSE GHOST. The thinnest, palest field — a barely-there dusty pink
// lattice, present only as a breath. Sparse. Contemplative.
{ name: '07_rose-ghost-quiet', qftSeed: 'GAUGE-2046',
qftOver: { cubicN: 1, e8Count: 1, e8OriginRadius: 0.66, linkCount: 4,
fields: { cubic: F(0.93, 0.90, 0.28, 0.66, 0.40, 0.45), schlegel: F(0.90, 0.94, 0.24, 0.64, 0.34, 0.45),
e8: F(0.92, 0.96, 0.30, 0.66, 0.42, 0.5), ripple: OFF, links: F(0.94, 0.90, 0.34, 0.64, 0.42, 0.65) } } },
// 8 — PALE SPECTRAL. A faint rainbow lattice — cubic cool-blue, schlegel
// violet, e8 gold, links rose: an earned-colour field, softened to a haze.
{ name: '08_pale-spectral-lattice', qftSeed: 'LATTICE-1003',
qftOver: { cubicN: 2, e8Count: 3, e8OriginRadius: 0.6, linkCount: 7,
fields: { cubic: F(0.55, 0.60, 0.40, 0.58, 0.46, 0.5), schlegel: F(0.72, 0.78, 0.42, 0.54, 0.46, 0.55),
e8: F(0.11, 0.14, 0.50, 0.58, 0.50, 0.6), ripple: OFF, links: F(0.93, 0.88, 0.45, 0.58, 0.5, 0.8) } } },
// 9 — CYAN BLUEPRINT. Pale cyan/blue draughtsman's lines with expanding
// ripple wavefronts — the schematic under the photograph.
{ name: '09_cyan-blueprint-ripples', qftSeed: 'PROPAGATOR-2755',
qftOver: { cubicN: 2, e8Count: 0, linkCount: 4, ripples: [R(0, 0, 5, 0.1, 0.11)],
fields: { cubic: F(0.54, 0.57, 0.45, 0.52, 0.52, 0.5), schlegel: F(0.56, 0.53, 0.42, 0.50, 0.45, 0.55),
e8: OFF, ripple: F(0.55, 0.58, 0.50, 0.54, 0.55, 0.6), links: F(0.54, 0.57, 0.50, 0.50, 0.55, 0.8) } } },
// 10 — COMMUNION. The field sweeps the EXACT purple→magenta→pink band of the
// trace family, at lowest saturation — field and trace are one colour,
// one chemistry; only the burnt-orange disk stands apart. ★
{ name: '10_trace-family-communion', qftSeed: 'FEYNMAN-7167',
qftOver: { cubicN: 2, e8Style: 'nautilus', e8Count: 3, nautilusTurns: 2.8, nautilusPerTurn: 16, nautilusGrowth: 0.2, linkCount: 7,
fields: { cubic: F(0.82, 0.90, 0.40, 0.50, 0.60, 0.55), schlegel: F(0.79, 0.86, 0.38, 0.48, 0.54, 0.55),
e8: F(0.88, 0.95, 0.44, 0.50, 0.64, 0.6), ripple: OFF, links: F(0.90, 0.82, 0.50, 0.48, 0.66, 0.8) } } },
];
console.log(`Compositing ${VARIATIONS.length} magenta-cream QFT×BC plates → ${OUT}/`);
for (const v of VARIATIONS) composite(v);
const cap = (v) => v.name.replace(/^\d+_/, '').replace(/-/g, ' ');
const idx = `<!DOCTYPE html><html><head><meta charset="utf-8"><title>QFT × Bubble Chamber · sketch 02 · magenta-cream</title>
<style>body{margin:0;background:#0b0b0b;color:#bbb;font:12px/1.5 ui-monospace,Menlo,monospace;padding:26px;max-width:1900px}
h1{font-weight:400;letter-spacing:.22em;text-transform:uppercase;font-size:13px;color:#d6a5c4}
.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(460px,1fr));gap:20px;margin-top:18px}
figure{margin:0;background:#fff;border:1px solid #262626;overflow:hidden}img{width:100%;display:block}
figcaption{padding:8px 10px;color:#e8e4d8;background:#111}small{color:#777;display:block}
.notes{color:#999;background:#180e16;padding:14px 18px;border-left:3px solid #d6a5c4;margin:18px 0}</style></head><body>
<h1>QFT × Bubble Chamber · sketch 02 — thin field between cream & the magenta event</h1>
<div class="notes">Built on <b>78_final-lively-magenta-cream</b> (MESON-5113 · magentarise · cream). A thin-line but
present QFT lattice is slipped between the cream ground and the bubble chamber (multiply on top). The event is constant;
each frame varies the QFT geometry and a hue family chosen to converse with the magenta tracks + burnt-orange disk.</div>
<div class=grid>
${VARIATIONS.map(v => `<figure><img src="${v.name}.svg"><figcaption>${cap(v)}<small>field: ${v.qftSeed}</small></figcaption></figure>`).join('\n')}
</div></body></html>`;
writeFileSync(`${OUT}/index.html`, idx);
const m = `<!DOCTYPE html><html><head><meta charset="utf-8">
<style>html,body{margin:0;background:#222}.grid{display:grid;grid-template-columns:repeat(2,1fr);gap:8px;padding:10px;width:2400px}figure{margin:0;position:relative;background:#fff;overflow:hidden}img{width:100%;display:block}figcaption{position:absolute;left:0;bottom:0;right:0;padding:6px 10px;font:14px ui-monospace,monospace;color:#fff;background:linear-gradient(transparent,#000d)}</style></head><body>
<div class="grid">
${VARIATIONS.map(v => `<figure><img src="${v.name}.svg"><figcaption>${cap(v)}</figcaption></figure>`).join('\n')}
</div></body></html>`;
writeFileSync(`${OUT}/m.html`, m);
console.log(`contact sheets -> ${OUT}/index.html , m.html`);

286
tools/qft-bc-variations.mjs Normal file
View File

@@ -0,0 +1,286 @@
/* ============================================================
qft-bc-variations.mjs — sketch 01 of the JOINED series.
A QFT field plate UNDERNEATH a bubble-chamber plate, composited
as two self-contained data-URI <image>s in one outer SVG (no
defs/id collision). The bubble chamber sits on top with a chosen
mix-blend-mode — the lightbox metaphor: the field is the ground
that authors the trace; the trace is the evidence on top.
Levers (per variation):
blend 'multiply'|'screen'|'darken'|'normal'|'hard-light'
bg outer background (matters for screen on dark)
qScale QFT image scale about centre (>1 bleeds off-frame)
qDx,qDy QFT pixel offset
qOpacity, bcOpacity
qftOver / bcOver param overrides merged into each side
Furniture is deduplicated: by default the QFT archival header is
OFF and the bubble chamber is the "studied / labelled" top plate.
Usage: node tools/qft-bc-variations.mjs [size]
============================================================ */
import { writeFileSync, mkdirSync } from 'node:fs';
import { generateQFTScene } from '../src/qft/scene.js';
import { paramsFromSeed as qftParams } from '../src/qft/params.js';
import { renderQFTSVG } from '../src/qft/renderer.js';
import { generateScene } from '../src/scene/scene.js';
import { renderSVG } from '../src/render/svgVector.js';
import { paramsFromSeed as bcParams } from '../src/scene/params.js';
import { GROUPS, TOGGLES, FIXED } from '../src/ui/controls.js';
const SIZE = +(process.argv[2] || 1700);
const OUT = 'output/qft-bc/sketch01';
mkdirSync(OUT, { recursive: true });
// --- helpers -------------------------------------------------
// F(hueStart,hueEnd,sat,light,opacity[,stroke]) — one QFT field's look.
const F = (hueStart, hueEnd, saturation, lightness, opacity, stroke) => {
const f = { hueStart, hueEnd, saturation, lightness, opacity };
if (stroke != null) f.stroke = stroke;
return f;
};
const OFF = F(0, 0, 0, 0.5, 0);
const paper = (flat, gi = [16, 14, 12], go = [-22, -20, -18]) => ({
flat,
glowIn: [flat[0] + gi[0], flat[1] + gi[1], flat[2] + gi[2]],
glowOut: [flat[0] + go[0], flat[1] + go[1], flat[2] + go[2]],
});
const W = (x, y, amplitude, sigma) => ({ x, y, amplitude, sigma });
const VX = (x, y, strength, sigma) => ({ x, y, strength, sigma });
const SW = (kx, ky, amplitude, phase = 0) => ({ kx, ky, amplitude, phase });
function bcBase(seed) {
const p = { ...FIXED, ...bcParams(seed) };
for (const g of GROUPS) for (const c of g.controls) if (!(c.id in p)) p[c.id] = c.value;
for (const t of TOGGLES) if (!(t.id in p)) p[t.id] = t.value;
p.showHeader = false; // off unless a variation opts in
return p;
}
const dataUri = (svg) => 'data:image/svg+xml;base64,' + Buffer.from(svg).toString('base64');
function composite(v) {
const qp = { ...qftParams(v.qftSeed), showHeader: false, glow: 0.6, ...(v.qftOver || {}) };
const bp = { ...bcBase(v.bcSeed), ...(v.bcOver || {}) };
// Tie BC polarity to the ground: positive (dark ink on light) for multiply/
// darken so the field shows through; negative (light tracks on black) for
// screen. Some seeds (e.g. NUCLEON) resolve to a negative by archetype, which
// would crush to black under multiply — override unless set explicitly.
const blend = v.blend || 'multiply';
if (!(v.bcOver && 'invert' in v.bcOver)) bp.invert = (blend !== 'screen');
const qftSvg = renderQFTSVG(generateQFTScene(qp), qp, SIZE);
const bcSvg = renderSVG(generateScene(bp), bp, SIZE);
const s = v.qScale ?? 1;
const qw = SIZE * s, qh = SIZE * s;
const qx = (SIZE - qw) / 2 + (v.qDx || 0);
const qy = (SIZE - qh) / 2 + (v.qDy || 0);
const composite =
`<svg xmlns="http://www.w3.org/2000/svg" width="${SIZE}" height="${SIZE}" viewBox="0 0 ${SIZE} ${SIZE}">
<rect width="${SIZE}" height="${SIZE}" fill="${v.bg || '#ffffff'}"/>
<image x="${qx.toFixed(0)}" y="${qy.toFixed(0)}" width="${qw.toFixed(0)}" height="${qh.toFixed(0)}" href="${dataUri(qftSvg)}" opacity="${v.qOpacity ?? 1}"/>
<image x="0" y="0" width="${SIZE}" height="${SIZE}" href="${dataUri(bcSvg)}" opacity="${v.bcOpacity ?? 1}" style="mix-blend-mode:${blend}"/>
</svg>`;
writeFileSync(`${OUT}/${v.name}.svg`, composite);
console.log(` ${v.name} (qft=${v.qftSeed}/${qp.archetype} × bc=${v.bcSeed}, ${blend})`);
return composite;
}
// ============================================================
// 10 variations — each chases one feeling from the roadmap.
// ============================================================
const VARIATIONS = [
// 1 — AWE, plainly stated. The lightbox positive: faint cubic+links
// field under a clean mono event. The thesis image.
{
name: '01_lightbox-positive', blend: 'multiply', bg: '#efe9da',
qftSeed: 'VACUUM-5113', bcSeed: 'LAMBDA-2648',
qftOver: {
substrate: 'cream', cubicN: 1, e8Count: 1, e8OriginRadius: 0.62, linkCount: 6,
fields: {
cubic: F(0.55, 0.50, 0.30, 0.62, 0.42, 1.1), schlegel: F(0.90, 0.95, 0.22, 0.58, 0.34, 1.0),
e8: F(0.10, 0.14, 0.45, 0.60, 0.55, 1.2), ripple: OFF, links: F(0.10, 0.05, 0.80, 0.62, 0.70, 1.6),
},
},
bcOver: { palette: 'mono', showHeader: true },
},
// 2 — THE NUMINOUS / deep-sky. Negative void: a luminous field glows
// through a photographic NEGATIVE (light tracks on black) via screen.
{
name: '02_negative-void', blend: 'screen', bg: '#070709',
qftSeed: 'GAUGE-2046', bcSeed: 'HYPERON-8444',
qftOver: {
substrate: 'void', cubicN: 2, e8Count: 2, glow: 0.7,
fields: {
cubic: F(0.55, 0.62, 0.55, 0.50, 0.55, 1.0), schlegel: F(0.50, 0.58, 0.50, 0.46, 0.50, 1.2),
e8: F(0.08, 0.14, 0.65, 0.55, 0.70, 1.3), ripple: OFF, links: F(0.95, 0.88, 0.85, 0.58, 0.85, 1.8),
},
},
bcOver: { invert: false, palette: 'mono', glow: 0.6 },
},
// 3 — DEVOTION as a single chemistry. Cyanotype across BOTH layers; the
// seeds share a true name (·2755) — one event, two readings.
{
name: '03_cyanotype-communion', blend: 'screen', bg: '#0a1b33',
qftSeed: 'PROPAGATOR-2755', bcSeed: 'CASCADE-2755',
qftOver: {
substrate: 'cyanotype', cubicN: 2, e8Count: 3, e8OriginRadius: 0.55, glow: 0.55,
fields: {
cubic: F(0.58, 0.55, 0.30, 0.78, 0.50, 1.0), schlegel: F(0.58, 0.55, 0.28, 0.72, 0.45, 1.1),
e8: F(0.55, 0.52, 0.25, 0.85, 0.55, 1.1), ripple: OFF, links: F(0.55, 0.52, 0.20, 0.92, 0.65, 1.5),
},
},
bcOver: { palette: 'cyanotype', invert: false },
},
// 4 — VERTIGO of scale, soft. The field swollen to 1.5× and bled off
// every edge, faint — atmosphere/fog the event floats in.
{
name: '04_field-as-atmosphere', blend: 'multiply', bg: '#ece6d6',
qftSeed: 'FEYNMAN-7167', bcSeed: 'NUCLEON-2131',
qScale: 1.55, qOpacity: 0.62,
qftOver: {
substrate: 'cream', cubicN: 2, e8Count: 4, e8Style: 'nautilus',
nautilusTurns: 2.6, nautilusPerTurn: 14, nautilusGrowth: 0.22, e8OriginRadius: 0.7,
fields: {
cubic: F(0.55, 0.50, 0.22, 0.66, 0.40, 0.9), schlegel: F(0.90, 0.95, 0.18, 0.60, 0.30, 0.9),
e8: F(0.10, 0.14, 0.40, 0.62, 0.50, 1.0), ripple: OFF, links: F(0.10, 0.05, 0.60, 0.64, 0.45, 1.2),
},
},
bcOver: { palette: 'mono' },
},
// 5 — UNCANNY RECOGNITION. A single nautilus rosette placed LOW, directly
// behind the shock disk: the field's spiral and the chamber's mandala
// become the same shape. The rhyme across scales, literalised. ★
{
name: '05_scale-rhyme-nautilus', blend: 'multiply', bg: '#efe9da',
qftSeed: 'LATTICE-1003', bcSeed: 'HYPERON-8444',
qftOver: {
substrate: 'cream', e8Style: 'nautilus', e8Origins: [{ x: 0.0, y: 0.34 }],
e8Scale: 0.5, nautilusTurns: 3.2, nautilusPerTurn: 18, nautilusGrowth: 0.20,
cubicN: 1, linkCount: 0,
fields: {
cubic: F(0.55, 0.50, 0.20, 0.66, 0.26, 0.9), schlegel: OFF,
e8: F(0.08, 0.13, 0.55, 0.58, 0.78, 1.5), ripple: OFF, links: OFF,
},
},
bcOver: { palette: 'mono', shockY: 0.62, burst: 0.85 },
},
// 6 — MELANCHOLY / the archive. Sepia throughout; the chamber is the
// studied plate — grease-pencil marks, KODAK film edge, header.
{
name: '06_sepia-archive', blend: 'multiply', bg: '#e7dcc6',
qftSeed: 'VACUUM-5113', bcSeed: 'LAMBDA-2648',
qftOver: {
substrate: 'cream', paperOverride: paper([231, 220, 198]),
vignOverride: [120, 100, 70], cubicN: 1, e8Count: 2, linkCount: 5,
fields: {
cubic: F(0.09, 0.07, 0.35, 0.55, 0.40, 1.0), schlegel: F(0.08, 0.06, 0.30, 0.52, 0.32, 1.0),
e8: F(0.09, 0.07, 0.45, 0.50, 0.55, 1.1), ripple: OFF, links: F(0.06, 0.04, 0.55, 0.55, 0.55, 1.4),
},
},
bcOver: { palette: 'mono', paperTone: 'sepia', toneStrength: 0.8, showHeader: true, annotate: 0.85, filmEdge: true, reseau: 0.4 },
},
// 7 — TRANSCENDENCE earned. The chamber inked by particle TYPE (a physics
// legend); the field's links + rosettes tuned to the same warm/cool
// families, so colour means the same thing on both plates.
{
name: '07_earned-colour-kind', blend: 'multiply', bg: '#ece7d8',
qftSeed: 'GAUGE-2046', bcSeed: 'HYPERON-8444',
qftOver: {
substrate: 'cream', cubicN: 2, e8Count: 3, e8OriginRadius: 0.58,
fields: {
cubic: F(0.55, 0.60, 0.45, 0.58, 0.45, 1.0), schlegel: F(0.83, 0.88, 0.40, 0.55, 0.40, 1.1),
e8: F(0.12, 0.16, 0.55, 0.56, 0.62, 1.2), ripple: OFF, links: F(0.90, 0.82, 0.70, 0.58, 0.70, 1.6),
},
},
bcOver: { palette: 'kind', saturation: 1.05 },
},
// 8 — CONTEMPLATIVE STILLNESS. A standing-wave (Chladni) field — the
// drum membrane of space — under a quiet, sparse event.
{
name: '08_chladni-quiet', blend: 'multiply', bg: '#eae6da',
qftSeed: 'FEYNMAN-7167', bcSeed: 'NUCLEON-2131',
qftOver: {
substrate: 'cream', cubicN: 2, e8Count: 0, linkCount: 3, linkCurvature: 0.2,
standingWaves: [SW(6.0, 1.0, 0.040), SW(1.0, 6.0, 0.040)],
fields: {
cubic: F(0.52, 0.56, 0.30, 0.60, 0.55, 1.0), schlegel: F(0.55, 0.58, 0.26, 0.56, 0.42, 1.0),
e8: OFF, ripple: OFF, links: F(0.10, 0.06, 0.55, 0.60, 0.55, 1.3),
},
},
bcOver: { palette: 'mono', primaries: 6, burst: 0.32, cosmics: 2, sweepers: 1, deltaRate: 0.4, vdecay: 1 },
},
// 9 — ORDER vs CHAOS. A dense, ordered lattice swirled by a vortex is the
// true subject; a single rare event punctuates it. Cool grey plate.
{
name: '09_dense-lattice-rare-event', blend: 'multiply', bg: '#dadcdd',
qftSeed: 'LATTICE-1003', bcSeed: 'NUCLEON-2131',
qftOver: {
substrate: 'cream', paperOverride: paper([216, 219, 222]), vignOverride: [70, 75, 85],
cubicN: 2, cubicScale: 1.2, e8Count: 1, e8OriginRadius: 0.66, linkCount: 6,
vortices: [VX(0, 0, 0.9, 0.28)],
fields: {
cubic: F(0.56, 0.60, 0.30, 0.50, 0.62, 0.9), schlegel: F(0.58, 0.62, 0.26, 0.48, 0.50, 1.0),
e8: F(0.10, 0.14, 0.45, 0.52, 0.55, 1.1), ripple: OFF, links: F(0.55, 0.06, 0.70, 0.55, 0.62, 1.4),
},
},
bcOver: { palette: 'mono', primaries: 5, burst: 0.5, cosmics: 1, sweepers: 1, deltaRate: 0.5, vdecay: 1 },
},
// 10 — VERTIGO, hard. A huge 4D tesseract bleeding past every edge behind
// a tight, dense burst: atom and hypercube in one frame.
{
name: '10_vertigo-tesseract', blend: 'multiply', bg: '#eceadf',
qftSeed: 'PROPAGATOR-2755', bcSeed: 'HYPERON-8444',
qScale: 1.7, qDx: 60, qDy: -40,
qftOver: {
substrate: 'cream', cubicN: 1, e8Count: 0, linkCount: 0,
schlegelScale: 1.45, schlegelOuterR: 0.9, schlegelInnerR: 0.28, schlegelRot3D: 0.55,
fields: {
cubic: F(0.55, 0.50, 0.18, 0.66, 0.22, 0.9), schlegel: F(0.90, 0.96, 0.42, 0.54, 0.62, 1.3),
e8: OFF, ripple: OFF, links: OFF,
},
},
bcOver: { palette: 'mono', primaries: 22, burst: 0.92, deltaRate: 0.7 },
},
];
console.log(`Compositing ${VARIATIONS.length} QFT×BC plates → ${OUT}/`);
for (const v of VARIATIONS) composite(v);
// ---- contact sheets ----
const cap = (v) => v.name.replace(/^\d+_/, '').replace(/-/g, ' ');
const idx = `<!DOCTYPE html><html><head><meta charset="utf-8"><title>QFT × Bubble Chamber · sketch 01</title>
<style>body{margin:0;background:#0b0b0b;color:#bbb;font:12px/1.5 ui-monospace,Menlo,monospace;padding:26px;max-width:1900px}
h1{font-weight:400;letter-spacing:.22em;text-transform:uppercase;font-size:13px;color:#a5d4c9}
.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(460px,1fr));gap:20px;margin-top:18px}
figure{margin:0;background:#fff;border:1px solid #262626;overflow:hidden}img{width:100%;display:block}
figcaption{padding:8px 10px;color:#e8e4d8;background:#111}small{color:#777;display:block}
.notes{color:#999;background:#0e1816;padding:14px 18px;border-left:3px solid #a5d4c9;margin:18px 0}</style></head><body>
<h1>QFT × Bubble Chamber · sketch 01 — the field that authors the trace, under the trace</h1>
<div class="notes">Two independently-seeded plates stacked as one image: a QFT field plate UNDERNEATH a bubble-chamber plate,
joined with <code>mix-blend-mode</code> (multiply on light grounds = lightbox; screen on dark = luminous negative).
Furniture deduplicated — the bubble chamber is the studied, labelled top plate. Each frame chases one feeling.</div>
<div class=grid>
${VARIATIONS.map(v => `<figure><img src="${v.name}.svg"><figcaption>${cap(v)}<small>${v.qftSeed} × ${v.bcSeed} · ${v.blend || 'multiply'}</small></figcaption></figure>`).join('\n')}
</div></body></html>`;
writeFileSync(`${OUT}/index.html`, idx);
const m = `<!DOCTYPE html><html><head><meta charset="utf-8">
<style>html,body{margin:0;background:#222}.grid{display:grid;grid-template-columns:repeat(2,1fr);gap:8px;padding:10px;width:2400px}figure{margin:0;position:relative;background:#fff;overflow:hidden}img{width:100%;display:block}figcaption{position:absolute;left:0;bottom:0;right:0;padding:6px 10px;font:14px ui-monospace,monospace;color:#fff;background:linear-gradient(transparent,#000d)}</style></head><body>
<div class="grid">
${VARIATIONS.map(v => `<figure><img src="${v.name}.svg"><figcaption>${cap(v)}</figcaption></figure>`).join('\n')}
</div></body></html>`;
writeFileSync(`${OUT}/m.html`, m);
console.log(`contact sheets -> ${OUT}/index.html , m.html`);

49
tools/qft-carpet.mjs Normal file
View File

@@ -0,0 +1,49 @@
/* ============================================================
qft-carpet.mjs — render VACUUM CARPET studies + a plexi deck.
Logic lives in src/qft/carpet.js (shared with tools/layering.mjs).
Usage: node tools/qft-carpet.mjs [size]
============================================================ */
import { writeFileSync, mkdirSync } from 'node:fs';
import { carpetSVG } from '../src/qft/carpet.js';
const SIZE = +(process.argv[2] || 1500);
const ROOT = 'output/qft/carpet';
mkdirSync(`${ROOT}/explore`, { recursive: true });
mkdirSync(`${ROOT}/layers`, { recursive: true });
// EXPLORE — soft sinusoidal/spiralling blips across the chaos range
const EXPLORE = [
{ name: '01_calm-ridgeline', label: 'calm · low-q swells, few blips', o: { chaos: 0.22, blips: 0.5, rows: 44, overlap: 1.6 } },
{ name: '02_vacuum-seethe', label: 'vacuum seethe · many spiralling blips', o: { chaos: 0.8, blips: 1.4, rows: 50, overlap: 1.8, salt: 'seethe' } },
{ name: '03_dense-fine', label: 'dense fine weave', o: { chaos: 0.5, blips: 1.0, rows: 70, overlap: 1.5, strokeNear: 1.2, salt: 'fine' } },
{ name: '04_sparse-bold', label: 'sparse · bold soft swells', o: { chaos: 0.55, blips: 0.9, rows: 30, overlap: 2.2, strokeNear: 2.2, salt: 'bold' } },
{ name: '05_deep-horizon', label: 'deep horizon · more sky above the mound', o: { chaos: 0.5, blips: 0.9, rows: 56, horizon: 0.44, wFar: 0.58, wNear: 0.74, salt: 'deep' } },
{ name: '06_verdigris-seethe', label: 'verdigris vacuum · oxidised', o: { chaos: 0.7, blips: 1.2, rows: 48, hue: 0.40, hue2: 0.47, sat: 0.42, salt: 'verd' } },
];
console.log(`carpet · explore (${EXPLORE.length}) → ${ROOT}/explore/`);
for (const v of EXPLORE) {
writeFileSync(`${ROOT}/explore/${v.name}.svg`, carpetSVG(SIZE, { ...v.o, mode: 'solid' }));
console.log(` ${v.name}${v.label}`);
}
{
const cap = (v) => `${v.name.replace(/^\d+_/, '').replace(/-/g, ' ')} · ${v.label}`;
writeFileSync(`${ROOT}/explore/m.html`, `<!DOCTYPE html><html><head><meta charset="utf-8">
<style>html,body{margin:0;background:#222}.grid{display:grid;grid-template-columns:repeat(2,1fr);gap:8px;padding:10px;width:2000px}figure{margin:0;position:relative;background:#fff;overflow:hidden}img{width:100%;display:block}figcaption{position:absolute;left:0;bottom:0;right:0;padding:6px 10px;font:13px ui-monospace,monospace;color:#fff;background:linear-gradient(transparent,#000d)}</style></head><body>
<div class="grid">${EXPLORE.map(v => `<figure><img src="${v.name}.svg"><figcaption>${cap(v)}</figcaption></figure>`).join('')}</div></body></html>`);
}
// LAYERS — three transparent PLATE sheets for a spaced plexi deck
const DECK = [
{ name: 'L3_back', salt: 'fieldA', hue: 0.58, hue2: 0.62, chaos: 0.55, blips: 0.8 },
{ name: 'L2_mid', salt: 'fieldB', hue: 0.52, hue2: 0.56, chaos: 0.65, blips: 1.0 },
{ name: 'L1_front', salt: 'fieldC', hue: 0.47, hue2: 0.50, chaos: 0.75, blips: 1.2 },
];
const deckBase = { mode: 'plate', rows: 46, horizon: 0.36, wFar: 0.58, wNear: 0.7, overlap: 1.7, mound: 0.4, sat: 0.6, lightNear: 0.33, lightFar: 0.55 };
console.log(`carpet · plexi deck (${DECK.length}) → ${ROOT}/layers/`);
for (const v of DECK) { writeFileSync(`${ROOT}/layers/${v.name}.svg`, carpetSVG(SIZE, { ...deckBase, ...v })); console.log(` ${v.name}`); }
writeFileSync(`${ROOT}/stack.html`, `<!DOCTYPE html><html><head><meta charset="utf-8"><title>vacuum carpet · plexi deck</title>
<style>html,body{margin:0;background:#0c0c0c}.stage{position:relative;width:${SIZE}px;height:${SIZE}px;margin:30px auto;background:rgb(226,219,199);box-shadow:0 0 120px #000 inset}.stage img{position:absolute;inset:0;width:100%;height:100%}.L3{filter:blur(2.2px);opacity:.5;transform:translateY(-6px) scale(1.01)}.L2{filter:blur(1px);opacity:.72;transform:translateY(-2px)}.L1{opacity:.95}.cap{max-width:${SIZE}px;margin:0 auto;color:#888;font:12px ui-monospace,monospace;padding:0 4px}</style></head><body>
<div class="stage"><img class="L3" src="layers/L3_back.svg"><img class="L2" src="layers/L2_mid.svg"><img class="L1" src="layers/L1_front.svg"></div>
<p class="cap">deck (back→front): film/diffusion · L3 · L2 · L1 · [bubble chamber]. back sheets blurred+dimmed = air-gap depth of field.</p></body></html>`);
console.log(`stack -> ${ROOT}/stack.html ; explore -> ${ROOT}/explore/m.html`);

View File

@@ -0,0 +1,71 @@
/* ============================================================
qft-infinite-sweep.mjs — workshop the cartesian grid as INFINITE
space: wide/deep/shallow slabs + camera pushed into the lattice +
exaggerated perspective, so the field bleeds off every edge and
rushes to a vanishing point rather than reading as a closed cube.
Usage: node tools/qft-infinite-sweep.mjs [size]
============================================================ */
import { writeFileSync, mkdirSync } from 'node:fs';
import { generateQFTScene } from '../src/qft/scene.js';
import { paramsFromSeed } from '../src/qft/params.js';
import { renderQFTSVG } from '../src/qft/renderer.js';
const SIZE = +(process.argv[2] || 1200);
const OUT = 'output/qft/infinite';
mkdirSync(OUT, { recursive: true });
const SEED = 'LATTICE-1003';
const D = Math.PI / 180;
const F = (h0, h1, s, l, o, st) => ({ hueStart: h0, hueEnd: h1, saturation: s, lightness: l, opacity: o, stroke: st });
const OFF = F(0, 0, 0, 0.5, 0, 0);
const BASE = {
substrate: 'cream', showHeader: false, glow: 0.16, vign: 0.14,
photonCyclesPerUnit: 6, segmentsPerEdge: 10, stroke: 1.0,
cubicRot: 0, linkCount: 0, e8Count: 0,
fields: { cubic: F(0.52, 0.57, 0.6, 0.38, 1.0, 1.0), schlegel: OFF, e8: OFF, ripple: OFF, links: OFF },
};
// each: nx/ny/nz extents, camera, scale, originY (vertical placement)
const V = (name, label, o) => ({ name, label, o });
const SWEEP = [
V('01_floor-to-horizon', 'flat floor · low pitch · rushes to a horizon',
{ nx: 9, ny: 0, nz: 20, yaw: 0, pitch: 13 * D, persp: 1.4, dist: 1.6, scale: 1.0, oy: 0.28 }),
V('02_floor-three-quarter', 'floor · 3/4 view · vanishing to the side',
{ nx: 11, ny: 0, nz: 18, yaw: -26 * D, pitch: 18 * D, persp: 1.25, dist: 1.9, scale: 0.9, oy: 0.22 }),
V('03_tunnel-inside', 'camera INSIDE · walls/floor/ceiling rush inward',
{ nx: 4, ny: 4, nz: 18, yaw: 0, pitch: 0, persp: 1.5, dist: 1.5, zShift: 7, scale: 0.62, oy: 0 }),
V('04_corridor-3q', 'corridor · slight angle · one-point-ish',
{ nx: 5, ny: 3, nz: 18, yaw: -18 * D, pitch: 7 * D, persp: 1.4, dist: 1.7, zShift: 3, scale: 0.72, oy: 0.05 }),
V('05_exaggerated-bleed', 'full lattice · strong persp · near corner blows off-page',
{ nx: 5, ny: 5, nz: 6, yaw: -40 * D, pitch: 30 * D, persp: 1.7, dist: 1.25, scale: 0.8, oy: 0 }),
V('06_diagonal-rush', 'diagonal rush across the frame',
{ nx: 9, ny: 2, nz: 18, yaw: 33 * D, pitch: 22 * D, persp: 1.5, dist: 1.55, scale: 0.82, oy: 0.05 }),
V('07_vaulted-ceiling', 'looking UP · ceiling lattice vaulting away',
{ nx: 9, ny: 0, nz: 18, yaw: 0, pitch: -15 * D, persp: 1.35, dist: 1.7, scale: 1.0, oy: -0.26 }),
V('08_deep-floor-strong', 'widest, deepest floor · strongest vanishing',
{ nx: 13, ny: 0, nz: 24, yaw: 0, pitch: 10 * D, persp: 1.6, dist: 1.35, scale: 1.0, oy: 0.3 }),
];
console.log(`Infinite-field sweep (${SWEEP.length}) → ${OUT}/ seed=${SEED}`);
for (const v of SWEEP) {
const o = v.o;
const p = {
...paramsFromSeed(SEED), ...BASE,
cubicNx: o.nx, cubicNy: o.ny, cubicNz: o.nz,
cubicYaw: o.yaw ?? 0, cubicPitch: o.pitch ?? 0, cubicRoll: o.roll ?? 0,
cubicPersp: o.persp ?? 0, cubicDist: o.dist ?? 3.4, cubicZShift: o.zShift ?? 0,
cubicScale: o.scale ?? 1.0, cubicOriginX: o.ox ?? 0, cubicOriginY: o.oy ?? 0,
};
writeFileSync(`${OUT}/${v.name}.svg`, renderQFTSVG(generateQFTScene(p), p, SIZE));
console.log(` ${v.name}${v.label}`);
}
const cap = (v) => `${v.name.replace(/^\d+_/, '').replace(/-/g, ' ')} · ${v.label}`;
const m = `<!DOCTYPE html><html><head><meta charset="utf-8">
<style>html,body{margin:0;background:#222}.grid{display:grid;grid-template-columns:repeat(2,1fr);gap:8px;padding:10px;width:2000px}figure{margin:0;position:relative;background:#fff;overflow:hidden}img{width:100%;display:block}figcaption{position:absolute;left:0;bottom:0;right:0;padding:6px 10px;font:13px ui-monospace,monospace;color:#fff;background:linear-gradient(transparent,#000d)}</style></head><body>
<div class="grid">
${SWEEP.map(v => `<figure><img src="${v.name}.svg"><figcaption>${cap(v)}</figcaption></figure>`).join('\n')}
</div></body></html>`;
writeFileSync(`${OUT}/m.html`, m);
console.log(`contact sheet -> ${OUT}/m.html`);

View File

@@ -0,0 +1,71 @@
/* ============================================================
qft-perspective-sweep.mjs — workshop the QFT cartesian/wavy grid
viewpoint. Isolates the cubic lattice (other fields off) and sweeps
the new camera: yaw / pitch / roll / perspective / distance.
Usage: node tools/qft-perspective-sweep.mjs [size]
============================================================ */
import { writeFileSync, mkdirSync } from 'node:fs';
import { generateQFTScene } from '../src/qft/scene.js';
import { paramsFromSeed } from '../src/qft/params.js';
import { renderQFTSVG } from '../src/qft/renderer.js';
const SIZE = +(process.argv[2] || 1100);
const OUT = 'output/qft/perspective';
mkdirSync(OUT, { recursive: true });
const SEED = 'LATTICE-1003';
const F = (h0, h1, s, l, o, st) => ({ hueStart: h0, hueEnd: h1, saturation: s, lightness: l, opacity: o, stroke: st });
const OFF = F(0, 0, 0, 0.5, 0, 0);
// isolate the cartesian grid: cubic only, mid teal, thin-ish wavy photon edges
const BASE = {
substrate: 'cream', showHeader: false, glow: 0.16, vign: 0.12,
cubicN: 1, photonCyclesPerUnit: 7, segmentsPerEdge: 12, stroke: 1.3,
cubicScale: 1.05, cubicRot: 0, linkCount: 0, e8Count: 0,
fields: {
cubic: F(0.52, 0.57, 0.6, 0.34, 1.0, 1.3), schlegel: OFF, e8: OFF, ripple: OFF, links: OFF,
},
};
const D = Math.PI / 180;
const cam = (name, label, c) => ({ name, label, cam: c });
const SWEEP = [
// baseline isometric
cam('01_iso-default', 'isometric · default 3/4', { }),
// yaw (spin) at iso pitch
cam('02_yaw-front', 'yaw 0° · facing a face', { yaw: 0, pitch: 35 * D }),
cam('03_yaw-deep', 'yaw -70° · spun round', { yaw: -70 * D, pitch: 35 * D }),
// pitch (tip)
cam('04_pitch-low', 'pitch 12° · near eye-level', { yaw: -45 * D, pitch: 12 * D }),
cam('05_pitch-steep', 'pitch 58° · looking down', { yaw: -45 * D, pitch: 58 * D }),
cam('06_pitch-top', 'pitch 78° · near top-down', { yaw: -45 * D, pitch: 78 * D }),
// perspective (vanishing point) at the 3/4 angle
cam('07_persp-mild', 'perspective 0.45 · gentle depth', { persp: 0.45, dist: 4.2 }),
cam('08_persp-strong','perspective 0.9 · dist 2.6 · dramatic', { persp: 0.9, dist: 2.6 }),
cam('09_persp-corner','into a corner · 1-pt-ish', { yaw: 30 * D, pitch: 30 * D, persp: 0.7, dist: 3.0 }),
// roll / cant
cam('10_roll-cant', 'roll 18° · canted + persp 0.4', { roll: 18 * D, persp: 0.4, dist: 3.6 }),
// dramatic hero angles
cam('11_hero-tunnel', 'low + strong persp · tunnel', { yaw: -45 * D, pitch: 18 * D, persp: 0.95, dist: 2.3 }),
cam('12_hero-vault', 'steep + persp · vaulted ceiling', { yaw: -20 * D, pitch: 62 * D, persp: 0.8, dist: 2.8 }),
];
console.log(`Perspective sweep (${SWEEP.length}) → ${OUT}/ seed=${SEED}`);
for (const v of SWEEP) {
const p = {
...paramsFromSeed(SEED), ...BASE,
cubicYaw: v.cam.yaw ?? -45 * D, cubicPitch: v.cam.pitch ?? 35.26 * D,
cubicRoll: v.cam.roll ?? 0, cubicPersp: v.cam.persp ?? 0, cubicDist: v.cam.dist ?? 3.4,
};
writeFileSync(`${OUT}/${v.name}.svg`, renderQFTSVG(generateQFTScene(p), p, SIZE));
console.log(` ${v.name}${v.label}`);
}
const cap = (v) => `${v.name.replace(/^\d+_/, '').replace(/-/g, ' ')} · ${v.label}`;
const m = `<!DOCTYPE html><html><head><meta charset="utf-8">
<style>html,body{margin:0;background:#222}.grid{display:grid;grid-template-columns:repeat(3,1fr);gap:8px;padding:10px;width:2100px}figure{margin:0;position:relative;background:#fff;overflow:hidden}img{width:100%;display:block}figcaption{position:absolute;left:0;bottom:0;right:0;padding:6px 10px;font:13px ui-monospace,monospace;color:#fff;background:linear-gradient(transparent,#000d)}</style></head><body>
<div class="grid">
${SWEEP.map(v => `<figure><img src="${v.name}.svg"><figcaption>${cap(v)}</figcaption></figure>`).join('\n')}
</div></body></html>`;
writeFileSync(`${OUT}/m.html`, m);
console.log(`contact sheet -> ${OUT}/m.html`);