feat(pet): web and iOS pet dot-glyph bodies, pixel whale, shared page (committed as-is; not yet built or tested by the committer)

Pet-model work in progress under pet/: dot field and glyph builds, pixel whale body, shared.html and pet-native bundles. pet/ build and conformance checks still to be run.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
This commit is contained in:
CodeWhale BotandClaude Sonnet 5.5 committed 2026-10-11 02:12:28 -07:00
1 parent dc8884455d
commit 3360a6d38d
12 files changed
+7938 -1479

No files matched your search

+10 -1
View File
@@ -36,13 +36,22 @@ In the running application:
| --- | --- |
| `/pet` | Toggle pet mode for this terminal: the habitat takes the whole content viewport now and on every accepted turn |
| `/pet on` / `/pet off` | Enable or disable explicitly; `off` closes the view and stops automatic entry while the companion keeps the pet alive |
| `/pet appearance` | Configure the shared palette in the authenticated local browser studio |
| `/pet appearance` | Configure the shared palette, whale style and large-text language in the authenticated local browser studio |
| `/pet style smooth\|pixel` | Choose the smooth aurora or pixel whale |
| `/pet language auto\|en\|zh-Hans\|zh-Hant\|ja` | Choose large activity phrases independently of the interface language |
| `/pet window` | Open the independently closable native macOS window |
| `/pet source` | Explicitly select this terminal session as the source |
| `/pet sound on\|off` | Request/release the companion's audio device |
| `/pet export` | Save a replay to this saved session's artifacts |
| `/pet status` | Mode, view, identity and measured terminal output counters |
Large text follows the interface language by default. English, Simplified
Chinese, Traditional Chinese and Japanese phrases are available; other
interface languages currently use English large text. A fixed choice lets an
English reader learn Chinese or Japanese phrases beside the normal English
status. Both whale styles turn their 980 drones into these same activity words.
The saved presentation settings leave the pet’s identity and recording intact.
The pet has no workbar panel; the habitat is its only terminal view. Escape
leaves it without cancelling the turn or touching the composer draft. F6
toggles sound, F8 opens the appearance studio, and F9 opens the native window;
+9 -7
View File
@@ -20,15 +20,17 @@ The generator verifies the source font SHA-256 before rasterization.
Each mask is 48 rows of 48 ASCII characters. '#' means ink; '.' means empty.
Coordinates are row-major, with x increasing right and y increasing down.
Glyphs use one shared font size, are individually centered without stretching,
Phrases use the largest fitting font size, are centered without stretching,
have at least two empty pixels of padding, and use a coverage threshold of 128.
reading=读, editing=写, searching=搜, testing=试, executing=行,
browsing=览, computer=控, memory=记, tool=用, thinking=思,
responding=答, delegating=协, waiting=待, done=成.
These are visual activity mnemonics; surrounding status text retains the
full action meaning. 成 is for observed successful completion.
The generator reads the English, Simplified Chinese, Traditional Chinese and
Japanese status and tool labels from crates/localization/locales/. The same
typed activity mapping is used by the terminal caption. Chinese labels of up
to three characters occupy one line; longer phrases occupy two centered
lines. English wraps at spaces and Japanese at phrase boundaries. No phrase
is abbreviated to a single character. Captions retain the full meaning in
the interface language independently of particle density, motion and color.
完成 is only for observed successful completion.
This Font Software is licensed under the SIL Open Font License,
Version 1.1.
+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" shape-rendering="crispEdges"><rect x="213.333" y="40.000" width="24.030" height="2.697" fill="#00D7E8"/><rect x="189.333" y="42.667" width="2.697" height="2.697" fill="#00D7E8"/><rect x="192.000" y="42.667" width="2.697" height="2.697" fill="#03A7FF"/><rect x="194.667" y="42.667" width="2.697" height="2.697" fill="#00D7E8"/><rect x="197.333" y="42.667" width="2.697" height="2.697" fill="#03A7FF"/><rect x="200.000" y="42.667" width="2.697" height="2.697" fill="#00D7E8"/><rect x="202.667" y="42.667" width="2.697" height="2.697" fill="#03A7FF"/><rect x="205.333" y="42.667" width="2.697" height="2.697" fill="#00D7E8"/><rect x="208.000" y="42.667" width="2.697" height="2.697" fill="#03A7FF"/><rect x="210.667" y="42.667" width="2.697" height="2.697" fill="#00D7E8"/><rect x="213.333" y="42.667" width="2.697" height="2.697" fill="#03A7FF"/><rect x="216.000" y="42.667" width="2.697" height="2.697" fill="#00D7E8"/><rect x="218.667" y="42.667" width="2.697" height="2.697" fill="#03A7FF"/><rect x="221.333" y="42.667" width="2.697" height="2.697" fill="#00D7E8"/><rect x="224.000" y="42.667" width="2.697" height="2.697" fill="#03A7FF"/><rect x="226.667" y="42.667" width="2.697" height="2.697" fill="#00D7E8"/><rect x="229.333" y="42.667" width="2.697" height="2.697" fill="#03A7FF"/><rect x="232.000" y="42.667" width="2.697" height="2.697" fill="#00D7E8"/><rect x="234.667" y="42.667" width="2.697" height="2.697" fill="#03A7FF"/><rect x="237.333" y="42.667" width="2.697" height="2.697" fill="#00D7E8"/><rect x="240.000" y="42.667" width="2.697" height="2.697" fill="#03A7FF"/><rect x="242.667" y="42.667" width="2.697" height="2.697" fill="#00D7E8"/><rect x="245.333" y="42.667" width="2.697" height="2.697" fill="#03A7FF"/><rect x="248.000" y="42.667" width="8.030" height="2.697" fill="#00D7E8"/><rect x="256.000" y="42.667" width="2.697" height="2.697" fill="#03A7FF"/><rect x="178.667" y="45.333" width="2.697" height="2.697" fill="#03A7FF"/><rect x="181.333" y="45.333" width="2.697" height="2.697" fill="#00D7E8"/><rect x="184.000" y="45.333" width="2.697" height="2.697" fill="#03A7FF"/><rect x="186.667" y="45.333" width="2.697" height="2.697" fill="#00D7E8"/><rect x="189.333" y="45.333" width="2.697" height="2.697" fill="#03A7FF"/><rect x="192.000" y="45.333" width="2.697" height="2.697" fill="#00D7E8"/><rect x="194.667" y="45.333" width="2.697" height="2.697" fill="#03A7FF"/><rect x="197.333" y="45.333" width="2.697" height="2.697" fill="#00D7E8"/><rect x="200.000" y="45.333" width="2.697" height="2.697" fill="#03A7FF"/><rect x="202.667" y="45.333" width="2.697" height="2.697" fill="#00D7E8"/><rect x="205.333" y="45.333" width="2.697" height="2.697" fill="#03A7FF"/><rect x="208.000" y="45.333" width="8.030" height="2.697" fill="#00D7E8"/><rect x="216.000" y="45.333" width="2.697" height="2.697" fill="#03A7FF"/><rect x="218.667" y="45.333" width="8.030" height="2.697" fill="#00D7E8"/><rect x="226.667" y="45.333" width="2.697" height="2.697" fill="#03A7FF"/><rect x="229.333" y="45.333" width="42.697" height="2.697" fill="#00D7E8"/><rect x="168.000" y="48.000" width="5.363" height="2.697" fill="#03A7FF"/><rect x="173.333" y="48.000" width="2.697" height="2.697" fill="#00D7E8"/><rect x="176.000" y="48.000" width="8.030" height="2.697" fill="#03A7FF"/><rect x="184.000" y="48.000" width="2.697" height="2.697" fill="#00D7E8"/><rect x="186.667" y="48.000" width="2.697" height="2.697" fill="#03A7FF"/><rect x="189.333" y="48.000" width="2.697" height="2.697" fill="#00D7E8"/><rect x="192.000" y="48.000" width="2.697" height="2.697" fill="#03A7FF"/><rect x="194.667" y="48.000" width="2.697" height="2.697" fill="#00D7E8"/><rect x="197.333" y="48.000" width="2.697" height="2.697" fill="#03A7FF"/><rect x="200.000" y="48.000" width="2.697" height="2.697" fill="#00D7E8"/><rect x="202.667" y="48.000" width="2.697" height="2.697" fill="#03A7FF"/><rect x="205.333" y="48.000" width="2.697" height="2.697" fill="#00D7E8"/><rect x="208.000" y="48.000" width="2.697" height="2.697" fill="#03A7FF"/><rect x="210.667" y="48.000" width="2.697" height="2.697" fill="#00D7E8"/><rect x="213.333" y="48.000" width="2.697" height="2.697" fill="#03A7FF"/><rect x="216.000" y="48.000" width="2.697" height="2.697" fill="#00D7E8"/><rect x="218.667" y="48.000" width="2.697" height="2.697" fill="#03A7FF"/><rect x="221.333" y="48.000" width="2.697" height="2.697" fill="#00D7E8"/><rect x="224.000" y="48.000" width="2.697" height="2.697" fill="#03A7FF"/><rect x="226.667" y="48.000" width="2.697" height="2.697" fill="#00D7E8"/><rect x="229.333" y="48.000" width="2.697" height="2.697" fill="#03A7FF"/><rect x="232.000" y="48.000" width="2.697" height="2.697" fill="#00D7E8"/><rect x="234.667" y="48.000" width="2.697" height="2.697" fill="#03A7FF"/><rect x="237.333" y="48.000" width="2.697" height="2.697" fill="#00D7E8"/><rect x="240.000" y="48.000" width="2.697" height="2.697" fill="#03A7FF"/><rect x="242.667" y="4Line truncated

After

Width:  |  Height:  |  Size: 482 KiB

File diff suppressed because it is too large. Load diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

+19 -15
View File
@@ -5,18 +5,18 @@
<style>
:root{color-scheme:dark;font:14px system-ui,sans-serif;background:#101619;color:#dae2e2;--line:#344044;--muted:#a4b3b5;--accent:#a3dcd4}*{box-sizing:border-box}[hidden]{display:none!important}body{margin:0}button,input,select{font:inherit}button{color:inherit;background:transparent;border:1px solid var(--line);border-radius:5px;padding:9px 13px;cursor:pointer}button:hover{background:#273338}button:disabled{opacity:.5;cursor:wait}button[aria-pressed=true]{border-color:var(--accent);background:#263b3a}button:focus-visible,input:focus-visible,select:focus-visible,canvas:focus-visible{outline:2px solid var(--accent);outline-offset:3px}::selection{background:#98d7cf;color:#142522}a{color:var(--accent);text-underline-offset:4px}header{height:74px;border-bottom:1px solid var(--line);display:flex;align-items:center;justify-content:space-between;padding:0 30px;gap:16px}header strong{font-size:18px;font-weight:550;letter-spacing:-.025em}header span{font-size:12px;color:var(--muted);margin-left:15px}nav{display:flex;gap:8px}main{display:grid;grid-template-columns:minmax(0,1fr) 290px;min-height:calc(100vh - 74px)}.stage{padding:28px;min-width:0}h1{font-size:30px;letter-spacing:-.03em;font-weight:500;margin:0 0 8px}p{color:var(--muted);line-height:1.6;margin:0 0 22px;max-width:68ch}.habitat{position:relative;height:calc(100vh - 282px);min-height:350px;background:#080f15;border:1px solid #374347;border-radius:5px;overflow:hidden}canvas{display:block;width:100%;height:100%;outline:none}.caption{position:absolute;left:25px;bottom:22px;right:25px;display:flex;justify-content:space-between;gap:12px;font:11px ui-monospace,monospace;color:#d1e1e2;pointer-events:none;font-variant-numeric:tabular-nums}.caption small{font:inherit}.controls{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-top:16px}.controls label{margin:0 8px}.identity{font:10px ui-monospace,monospace;color:var(--muted);margin-top:15px;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.settings{border-left:1px solid var(--line);padding:28px 24px;overflow-y:auto;max-height:calc(100vh - 74px);scrollbar-color:#55686a #101619}.settings h2{font-size:16px;font-weight:550;margin:0 0 8px}.settings p{font-size:12px;line-height:1.55;margin-bottom:22px}.presets{display:grid;grid-template-columns:1fr 1fr;gap:7px;margin-bottom:24px}.presets button{padding:9px;text-align:left;font-size:12px;display:flex;align-items:center;gap:8px}.swatch{width:18px;height:18px;display:inline-grid;place-items:center;border-radius:50%;border:1px solid #788887}.swatch i{width:7px;height:7px;border-radius:50%}.field{display:flex;align-items:center;justify-content:space-between;gap:10px;margin:17px 0;font-size:12px}.field input[type=color]{width:40px;height:26px;padding:0;border:1px solid var(--line);background:none;border-radius:3px;cursor:pointer}.range{display:block}.range span{display:flex;justify-content:space-between;margin-bottom:8px}.range input{width:100%;accent-color:var(--accent)}input[type=checkbox]{accent-color:var(--accent)}input[type=file]{max-width:100%;font-size:11px}output{font-variant-numeric:tabular-nums;color:var(--muted)}.divider{border-top:1px solid var(--line);margin-top:22px;padding-top:8px}.config-actions{display:flex;gap:8px;margin-top:20px}.config-actions button{font-size:11px;padding:8px}.status{min-height:32px;font-size:11px!important;margin-top:16px!important}.result{position:absolute;left:clamp(24px,9vw,160px);right:clamp(24px,9vw,160px);top:50%;max-height:44%;overflow:auto;padding:24px 30px;background:#111b20f2;color:#e4eeec;border-radius:6px;opacity:0;transform:translateY(18px);pointer-events:none;transition:opacity .65s cubic-bezier(.16,1,.3,1),transform .65s cubic-bezier(.16,1,.3,1);scrollbar-color:#65817f #111b20}.result.visible{opacity:1;transform:translateY(0);pointer-events:auto}.result h2{font-size:21px;font-weight:500;letter-spacing:-.02em;margin:0 0 12px}.result p{color:#b9cecc;font-size:14px;line-height:1.7;margin:0 0 14px}.result .label{font:11px ui-monospace,monospace;color:#94beb8;margin:0 0 12px}.result button{font-size:12px}.immersive header,.immersive .settings,.immersive .intro,.immersive .identity{display:none}.immersive main{display:block}.immersive .stage{padding:0}.immersive .habitat{height:calc(100dvh - 64px);min-height:200px;border:0;border-radius:0}.immersive .controls{height:64px;margin:0;padding:10px 22px}.gallery{display:none;grid-template-columns:repeat(4,minmax(0,1fr));gap:18px;margin:24px 0}.gallery figure{margin:0;height:205px;position:relative;border-radius:5px;overflow:hidden}.gallery figcaption{position:absolute;left:16px;bottom:14px;font-size:12px}.gallery-mode .gallery{display:grid}.gallery-mode .habitat{display:none}.gallery-mode .stage> .controls{display:none}.gallery-mode .settings{position:sticky;top:0}.connection{margin-top:12px;font-size:11px}.connection a{display:inline-block;margin-top:8px}.work-label{font:11px ui-monospace,monospace;color:var(--muted);margin-left:auto}#finish{display:none}.preview-runLine truncated
.activity{position:absolute;top:48px;left:30px;right:30px;color:#d4e5e5;pointer-events:none}.activity .eyebrow{font:12px ui-monospace,monospace;opacity:.75;margin-bottom:8px}.activity h2{font-size:clamp(22px,2.8vw,34px);font-weight:500;letter-spacing:-.035em;margin:0 0 8px}.activity .detail{font:12px ui-monospace,monospace;line-height:1.6;overflow-wrap:anywhere}.activity .parallel{display:block;margin-top:7px;font:12px system-ui,sans-serif}.result.visible~.activity{opacity:1}.action-field{display:block;font-size:12px;margin:18px 0 10px}.action-field select{display:block;width:100%;margin-top:8px;color:inherit;background:#182226;border:1px solid var(--line);padding:9px;border-radius:4px}.settings .behavior-note{font-size:12px;margin-bottom:24px}.caption{font-size:12px}.identity{font-size:11px}.status,.config-actions button,.connection{font-size:12px!important}.gallery figcaption{font-size:13px}@media(max-width:480px){.activity{top:38px;left:22px;right:22px}.activity h2{font-size:23px}.activity .detail{font-size:12px}.caption{font-size:12px}.result.visible~.activity{display:none}.immersive .habitat{height:calc(100dvh - 112px)}}
.activity{position:absolute;top:48px;left:30px;right:30px;color:#d4e5e5;pointer-events:none}.activity .phrase{font-size:16px;line-height:1.5;margin-bottom:7px}.activity .eyebrow{font:12px ui-monospace,monospace;opacity:.75;margin-bottom:8px}.activity h2{font-size:clamp(22px,2.8vw,34px);font-weight:500;letter-spacing:-.035em;margin:0 0 8px}.activity .detail{font:12px ui-monospace,monospace;line-height:1.6;overflow-wrap:anywhere}.activity .parallel{display:block;margin-top:7px;font:12px system-ui,sans-serif}.result.visible~.activity{opacity:1}.action-field{display:block;font-size:12px;margin:18px 0 10px}.action-field select{display:block;width:100%;margin-top:8px;color:inherit;background:#182226;border:1px solid var(--line);padding:9px;border-radius:4px}.settings .behavior-note{font-size:12px;margin-bottom:24px}.caption{font-size:12px}.identity{font-size:11px}.status,.config-actions button,.connection{font-size:12px!important}.gallery figcaption{font-size:13px}@media(max-width:480px){.activity{top:38px;left:22px;right:22px}.activity h2{font-size:23px}.activity .detail{font-size:12px}.caption{font-size:12px}.result.visible~.activity{display:none}.immersive .habitat{height:calc(100dvh - 112px)}}
/* A reliable backing protects labels even when the two user colors have
opposite luminance. Particle colors remain entirely configurable. */
.activity{width:max-content;max-width:calc(100% - 60px);padding:12px 15px;background:#10191df2;border-radius:5px;color:#eaf2f0}.activity .eyebrow{margin:0 0 8px;opacity:1;color:#b9ceca}.caption{padding:8px 10px;background:#10191df2;border-radius:4px;color:#eaf2f0}.result:focus{outline:2px solid #a3dcd4;outline-offset:3px}@media(max-width:480px){.activity{max-width:calc(100% - 44px);padding:12px}.activity h2{font-size:22px}}
.activity{width:max-content;max-width:calc(100% - 60px);padding:12px 15px;background:#10191df2;border-radius:5px;color:#eaf2f0}.activity .phrase{font-size:16px;line-height:1.5;margin-bottom:7px}.activity .eyebrow{margin:0 0 8px;opacity:1;color:#b9ceca}.caption{padding:8px 10px;background:#10191df2;border-radius:4px;color:#eaf2f0}.result:focus{outline:2px solid #a3dcd4;outline-offset:3px}@media(max-width:480px){.activity{max-width:calc(100% - 44px);padding:12px}.activity h2{font-size:22px}}
</style>
<header><div><strong>Codewhale pet</strong><span id="connection-label">Shared habitat</span></div><nav aria-label="View options"><button id="compare" aria-pressed="false">Compare looks</button><button id="configure" aria-pressed="true">Appearance</button></nav></header>
<main><section class="stage"><div class="intro"><h1>The whale speaks in light.</h1><p>The whole whale flows into a living character: reading scans 读, writing traces 写, and searching sweeps light through 搜. Then every dot gathers back into the whale.</p></div>
<div class="habitat"><canvas id="tank" tabindex="0" role="img" aria-label="Codewhale habitat"></canvas><article class="result" id="result" tabindex="-1" aria-live="polite" aria-hidden="true" inert><h2>The work is ready.</h2><div class="label">Preview result · no task was run</div><p>The whale held the space while the task was running. Now the answer comes forward, with the habitat still present behind it.</p><p>In Codewhale, this space will show the completed assistant response from your existing conversation. Your draft, history, and selection stay intact.</p><button id="replay">Replay transition</button></article><div class="activity" id="activity" aria-live="polite" aria-atomic="true"><h2 id="activity-title">A moment between tasks.</h2><div class="eyebrow" id="activity-evidence">Local companion</div><div class="detail" id="activity-detail">Activity unobserved</div><span class="parallel" id="activity-parallel"></span></div><div class="caption"><strong id="caption">Connecting…</strong><small id="clock"></small></div></div>
<main><section class="stage"><div class="intro"><h1>The whale speaks in light.</h1><p>Choose a whale style and a language for its large activity words. Its pixels and drones form the same phrases Codewhale uses, then return to the whale.</p></div>
<div class="habitat"><canvas id="tank" tabindex="0" role="img" aria-label="Codewhale habitat"></canvas><article class="result" id="result" tabindex="-1" aria-live="polite" aria-hidden="true" inert><h2>The work is ready.</h2><div class="label">Preview result · no task was run</div><p>The whale held the space while the task was running. Now the answer comes forward, with the habitat still present behind it.</p><p>In Codewhale, this space will show the completed assistant response from your existing conversation. Your draft, history, and selection stay intact.</p><button id="replay">Replay transition</button></article><div class="activity" id="activity" aria-live="polite" aria-atomic="true"><h2 id="activity-title">A moment between tasks.</h2><div class="phrase" id="activity-phrase" hidden></div><div class="eyebrow" id="activity-evidence">Local companion</div><div class="detail" id="activity-detail">Activity unobserved</div><span class="parallel" id="activity-parallel"></span></div><div class="caption"><strong id="caption">Connecting…</strong><small id="clock"></small></div></div>
<div class="gallery" id="gallery" aria-label="The same whale across eight appearances"></div>
<div class="controls"><button id="focus">Focus</button><button id="pulse">Pulse</button><button id="sound" aria-pressed="false">Sound off</button><label><input id="still" type="checkbox"> Still</label><button id="expand">Full habitat</button><button id="work">Preview work → result</button><button id="finish">Finish preview now</button><button id="return-live" hidden>Return to live pet</button><span class="work-label" id="work-label">Escape returns</span></div><div class="identity" id="identity"></div></section>
<aside class="settings" aria-label="Appearance settings"><h2>Behavior</h2><label class="action-field">Try a preview action<select id="action-preview"><option value="thinking">Thinking</option><option value="reading">Reading files</option><option value="searching">Searching</option><option value="editing">Editing files</option><option value="executing">Running a command</option><option value="testing">Running tests</option><option value="browsing">Using the browser</option><option value="computer">Controlling the desktop</option><option value="memory">Retrieving context</option><option value="tool">Using a tool</option><option value="delegating">Three parallel agents</option><option value="waiting">Waiting for you</option><option value="responding">Writing a response</option><option value="error">An operation failed</option><option value="unknown">No telemetry</option></select></label><p class="behavior-note">Preview actions are simulated. All 980 dots become one large character, alive with light and motion, before returning to the filled whale. Live activity comes from the session owner.</p><h2>Appearance</h2><p>Presets are starting points. Every color is yours to change.</p><div class="presets" id="presets"></div>
<aside class="settings" aria-label="Appearance settings"><h2>Behavior</h2><label class="action-field">Try a preview action<select id="action-preview"><option value="thinking">Thinking</option><option value="reading">Reading files</option><option value="searching">Searching</option><option value="editing">Editing files</option><option value="executing">Running a command</option><option value="testing">Running tests</option><option value="browsing">Using the browser</option><option value="computer">Controlling the desktop</option><option value="memory">Retrieving context</option><option value="tool">Using a tool</option><option value="delegating">Three parallel agents</option><option value="waiting">Waiting for you</option><option value="responding">Writing a response</option><option value="error">An operation failed</option><option value="unknown">No telemetry</option></select></label><p class="behavior-note">Preview actions are simulated. All 980 dots form complete activity phrases, alive with light and motion, before returning to the filled whale. Live activity comes from the session owner.</p><h2>Whale</h2><label class="action-field">Whale style<select id="bodyStyle"><option value="smooth">Smooth aurora</option><option value="pixel">Pixel whale</option></select></label><label class="action-field">Large-text language<select id="textLanguage"><option value="auto">Follow Codewhale interface</option><option value="en">English</option><option value="zh-Hans">简体中文 · Simplified Chinese</option><option value="zh-Hant">繁體中文 · Traditional Chinese</option><option value="ja">日本語 · Japanese</option></select></label><p class="behavior-note">Try another language to learn its activity phrases. Regular status text stays in the interface language. Other interface languages currently use English for the large text.</p><h2>Appearance</h2><p>Presets are starting points. Every color is yours to change.</p><div class="presets" id="presets"></div>
<label class="field">Background <input type="color" id="background" value="#080f15"></label><label class="field">Upper light <input type="color" id="backgroundTop" value="#182d38"></label><label class="field">Particle color <input type="color" id="particle" value="#87dddb"></label><label class="field"><span>Colors follow activity</span><input type="checkbox" id="eventColors" checked></label>
<label class="field range"><span>Brightness <output id="brightness-value"></output></span><input type="range" id="brightness" min="0.25" max="2" step="0.05" value="1.25"></label><label class="field range"><span>Dot size <output id="dotScale-value"></output></span><input type="range" id="dotScale" min="0.65" max="1.8" step="0.05" value="1"></label><label class="field range"><span>Glow <output id="glow-value"></output></span><input type="range" id="glow" min="0" max="1" step="0.05" value="0.5"></label><label class="field"><span>Water and horizon</span><input type="checkbox" id="environment" checked></label>
<div class="config-actions"><button id="export-config">Save appearance</button><button id="import-config">Load appearance</button><input id="config-file" type="file" accept="application/json,.json" hidden></div><p class="status" id="message" role="status">Opening the habitat…</p><div class="connection"><button id="save">Save pet replay</button><br><a href="pet.html" id="join">Join a live pet from the standalone viewer</a></div></aside></main>
@@ -25,20 +25,23 @@
'use strict';
const $=id=>document.getElementById(id), canvas=$('tank');
const rgb=h=>h.match(/[a-f\d]{2}/gi).map(n=>parseInt(n,16)),hex=c=>'#'+c.map(n=>n.toString(16).padStart(2,'0')).join('');
const base={background:[8,15,21],backgroundTop:[24,45,56],particle:[135,221,219],eventColors:true,brightness:1.25,dotScale:1,glow:.5,environment:true};
const base={background:[8,15,21],backgroundTop:[24,45,56],particle:[135,221,219],eventColors:true,brightness:1.25,dotScale:1,glow:.5,environment:true,bodyStyle:'smooth',textLanguage:'auto'};
const presets=[['Ocean','#080f15','#182d38','#87dddb'],['Chalk','#e8edee','#fbfdfb','#285967'],['Graphite','#141518','#28292e','#d9e0e3'],['Linen','#e9e1d2','#f8f2e7','#79573a'],['Forest','#0c1916','#20382d','#c5db9b'],['Plum','#201a29','#3b2e4b','#e7b8d0'],['Ember','#211610','#49291b','#edb177'],['Cobalt','#101934','#263b60','#b2d7f2']].map(([name,b,t,p])=>({name,...base,background:rgb(b),backgroundTop:rgb(t),particle:rgb(p),eventColors:false,brightness:name==='Chalk'||name==='Linen'?1.8:1.35}));
let appearance={...base}, selected='Ocean', current, previous, lastTickAt=0, received=0, client=crypto.randomUUID(), seq=0, pending=null, nextAppearance=null, busy=false, sound=false;
let preview=new URLSearchParams(location.search).has('preview'), pet, points, demoTick=0, demoRunning=false, demoDone=false, actionChoice='thinking', actionTick=0, lastDraw=0, accumulator=0, galleryTick=0, previewSaved=false;
const pixelWhale=new Image();pixelWhale.src='pixel-whale.webp';
const pixelTints=new Map();
function pixelSource(a){if(a.eventColors)return pixelWhale;const key=hex(a.particle);if(pixelTints.has(key))return pixelTints.get(key);const c=document.createElement('canvas');c.width=c.height=512;const ctx=c.getContext('2d');ctx.imageSmoothingEnabled=false;ctx.drawImage(pixelWhale,0,0,512,512);ctx.globalCompositeOperation='source-in';ctx.fillStyle=key;ctx.fillRect(0,0,512,512);if(pixelTints.size>=16)pixelTints.clear();pixelTints.set(key,c);return c}
const media=matchMedia('(prefers-reduced-motion: reduce)');$('still').checked=media.matches;media.onchange=()=>{$('still').checked=media.matches};
function valid(a){return a&&Object.keys(base).every(k=>k in a)&&['background','backgroundTop','particle'].every(k=>Array.isArray(a[k])&&a[k].length===3&&a[k].every(n=>Number.isInteger(n)&&n>=0&&n<=255))&&typeof a.eventColors==='boolean'&&typeof a.environment==='boolean'&&[['brightness',.25,2],['dotScale',.65,1.8],['glow',0,1]].every(([k,lo,hi])=>Number.isFinite(a[k])&&a[k]>=lo&&a[k]<=hi)}
function clean(a){if(!valid(a))throw Error('This appearance has invalid colors or control ranges.');return Object.fromEntries(Object.keys(base).map(k=>[k,a[k]]))}
function valid(a){return a&&Object.keys(base).filter(k=>!['bodyStyle','textLanguage'].includes(k)).every(k=>k in a)&&['background','backgroundTop','particle'].every(k=>Array.isArray(a[k])&&a[k].length===3&&a[k].every(n=>Number.isInteger(n)&&n>=0&&n<=255))&&typeof a.eventColors==='boolean'&&typeof a.environment==='boolean'&&['smooth','pixel'].includes(a.bodyStyle??'smooth')&&['auto','en','zh-Hans','zh-Hant','ja'].includes(a.textLanguage??'auto')&&[['brightness',.25,2],['dotScale',.65,1.8],['glow',0,1]].every(([k,lo,hi])=>Number.isFinite(a[k])&&a[k]>=lo&&a[k]<=hi)}
function clean(a){if(!valid(a))throw Error('This appearance has invalid colors or control ranges.');return Object.fromEntries(Object.keys(base).map(k=>[k,a[k]??base[k]]))}
try{const saved=JSON.parse(localStorage.getItem('codewhale-pet-appearance-v1'));if(valid(saved))appearance=clean(saved)}catch{}
function presetName(a){return presets.find(p=>JSON.stringify(clean(p))===JSON.stringify(clean(a)))?.name||'Custom'}
function presetName(a){const palette=v=>Object.fromEntries(Object.entries(clean(v)).filter(([k])=>!['bodyStyle','textLanguage'].includes(k)));return presets.find(p=>JSON.stringify(palette(p))===JSON.stringify(palette(a)))?.name||'Custom'}
selected=presetName(appearance);
function syncControls(){for(const k of ['background','backgroundTop','particle'])$(k).value=hex(appearance[k]);for(const k of ['eventColors','environment'])$(k).checked=appearance[k];for(const k of ['brightness','dotScale','glow']){$(k).value=appearance[k];$(k+'-value').value=k==='glow'?Math.round(appearance[k]*100)+'%':appearance[k].toFixed(2)+'×'}for(const b of $('presets').children)b.setAttribute('aria-pressed',String(b.textContent===selected));}
function syncControls(){for(const k of ['bodyStyle','textLanguage'])$(k).value=appearance[k];for(const k of ['background','backgroundTop','particle'])$(k).value=hex(appearance[k]);for(const k of ['eventColors','environment'])$(k).checked=appearance[k];for(const k of ['brightness','dotScale','glow']){$(k).value=appearance[k];$(k+'-value').value=k==='glow'?Math.round(appearance[k]*100)+'%':appearance[k].toFixed(2)+'×'}for(const b of $('presets').children)b.setAttribute('aria-pressed',String(b.textContent===selected));}
function saveAppearance(){try{localStorage.setItem('codewhale-pet-appearance-v1',JSON.stringify(appearance))}catch{}if(preview){$('message').textContent='Appearance saved in this preview. Export it to keep a portable copy.'}else{nextAppearance=clean(appearance);$('message').textContent='Saving appearance to the companion…';void flushAction()}}
for(const a of presets){const b=document.createElement('button');b.type='button';b.setAttribute('aria-pressed','false');const sw=document.createElement('span');sw.className='swatch';sw.style.background=hex(a.background);const dot=document.createElement('i');dot.style.background=hex(a.particle);sw.append(dot);b.append(sw,document.createTextNode(a.name));b.onclick=()=>{appearance=clean(a);selected=a.name;syncControls();saveAppearance()};$('presets').append(b);const f=document.createElement('figure'),c=document.createElement('canvas'),label=document.createElement('figcaption');c.setAttribute('role','img');c.setAttribute('aria-label',a.name+' appearance');label.textContent=a.name;label.style.color=readable(a.background);f.append(c,label);$('gallery').append(f)}
for(const key of Object.keys(base))$(key).oninput=()=>{appearance[key]=['background','backgroundTop','particle'].includes(key)?rgb($(key).value):['eventColors','environment'].includes(key)?$(key).checked:Number($(key).value);if(key==='particle')appearance.eventColors=false;selected='Custom';syncControls();saveAppearance()};
for(const a of presets){const b=document.createElement('button');b.type='button';b.setAttribute('aria-pressed','false');const sw=document.createElement('span');sw.className='swatch';sw.style.background=hex(a.background);const dot=document.createElement('i');dot.style.background=hex(a.particle);sw.append(dot);b.append(sw,document.createTextNode(a.name));b.onclick=()=>{appearance=clean({...a,bodyStyle:appearance.bodyStyle,textLanguage:appearance.textLanguage});selected=a.name;syncControls();saveAppearance()};$('presets').append(b);const f=document.createElement('figure'),c=document.createElement('canvas'),label=document.createElement('figcaption');c.setAttribute('role','img');c.setAttribute('aria-label',a.name+' appearance');label.textContent=a.name;label.style.color=readable(a.background);f.append(c,label);$('gallery').append(f)}
for(const key of Object.keys(base))$(key).oninput=()=>{appearance[key]=['bodyStyle','textLanguage'].includes(key)?$(key).value:['background','backgroundTop','particle'].includes(key)?rgb($(key).value):['eventColors','environment'].includes(key)?$(key).checked:Number($(key).value);if(key==='particle')appearance.eventColors=false;if(!['bodyStyle','textLanguage'].includes(key))selected='Custom';syncControls();saveAppearance()};
syncControls();
async function request(path,body,headers={}){const r=await fetch(path,{method:body===undefined?'GET':'POST',headers:{'Content-Type':'application/json',...headers},body:body===undefined?undefined:JSON.stringify(body),signal:AbortSignal.timeout(2500)});if(!r.ok){let e;try{e=await r.json()}catch{}const error=Error(e?.error||'Local pet unavailable. Reopen from Codewhale or use the preview.');error.rejected=r.status===409&&!error.message.includes('storage');throw error}return r.json()}
async function flushAction(){if(busy||!current||preview)return;if(!pending&&nextAppearance){pending={identity:current.identity,client,seq:seq+1,source_revision:current.sourceRevision,action:{kind:'appearance',appearance:nextAppearance}};nextAppearance=null}if(!pending)return;busy=true;try{await request('/v1/action',pending);seq=pending.seq;pending=null;$('message').textContent='Appearance and interactions saved by the shared companion.'}catch(e){if(e.rejected)pending=null;$('message').textContent=e.message}finally{busy=false}}
@@ -69,7 +72,7 @@ const previewActions={reading:'read_file',searching:'grep_files',editing:'apply_
const workPhases=['thinking','reading','searching','browsing','computer','memory','tool','delegating','editing','executing','testing','waiting','responding'];
function beginAction(kind,at){const emit=e=>pet.observeEngine(JSON.stringify(e),at);emit({event:'turn_started',turn_id:'preview-turn-'+(++previewTurn)});if(operationKinds.includes(kind))emit({event:'operation_activity_started',span_id:'preview-op-'+(++previewSpan),activity_kind:kind,action_id:previewActions[kind]});else if(kind==='waiting')emit({event:'user_input_required',id:'preview-question'});else if(kind==='delegating'){for(let i=0;i<3;i++)emit({event:'agent_spawned',id:'preview-worker-'+i})}else if(kind==='thinking'||kind==='responding')emit({event:kind==='thinking'?'thinking_started':'message_started',index:0});else if(kind==='error'){const span='preview-op-'+(++previewSpan);emit({event:'operation_activity_started',span_id:span,activity_kind:'executing',action_id:'exec_shell'});emit({event:'operation_activity_completed',span_id:span,activity_kind:'executing',action_id:'exec_shell',outcome:'failed'})}actionTick=0}
$('action-preview').onchange=async()=>{try{if(!preview)await makePreview();demoRunning=false;demoDone=false;actionChoice=$('action-preview').value;beginAction(actionChoice,demoTick*1000/30);showResult(false);document.body.classList.remove('preview-running')}catch(e){$('message').textContent=e.message}};
function advancePreview(){const at=demoTick*1000/30;if(!demoDone){if(demoRunning){const phase=Math.min(workPhases.length-1,Math.floor(demoTick/180));if(demoTick%180===0){actionChoice=workPhases[phase];$('action-preview').value=actionChoice;beginAction(actionChoice,at)}}else if(demoTick===0)beginAction(actionChoice,at);if(actionTick%6===0){const emit=e=>pet.observeEngine(JSON.stringify(e),at);if(operationKinds.includes(actionChoice))emit({event:'tool_call_heartbeat'});else if(actionChoice==='delegating'){for(let i=0;i<3;i++)emit({event:'agent_progress',id:'preview-worker-'+i,worker_status:'running'})}else if(actionChoice==='thinking'||actionChoice==='responding')emit({event:'response_delta',index:0,channel:actionChoice==='thinking'?'reasoning':'text'});}}actionTick++;pet.advanceEngine(++demoTick*1000/30,true,!demoDone&&actionChoice==='waiting');if(demoRunning&&demoTick>=180*workPhases.length)finishWork();previous=current;current=JSON.parse(pet.presentation());lastTickAt=received=performance.now();current.producerConnected=!demoDone&&actionChoice!=='unknown';current.epoch='preview';}
function advancePreview(){const at=demoTick*1000/30;if(!demoDone){if(demoRunning){const phase=Math.min(workPhases.length-1,Math.floor(demoTick/180));if(demoTick%180===0){actionChoice=workPhases[phase];$('action-preview').value=actionChoice;beginAction(actionChoice,at)}}else if(demoTick===0)beginAction(actionChoice,at);if(actionTick%6===0){const emit=e=>pet.observeEngine(JSON.stringify(e),at);if(operationKinds.includes(actionChoice))emit({event:'tool_call_heartbeat'});else if(actionChoice==='delegating'){for(let i=0;i<3;i++)emit({event:'agent_progress',id:'preview-worker-'+i,worker_status:'running'})}else if(actionChoice==='thinking'||actionChoice==='responding')emit({event:'response_delta',index:0,channel:actionChoice==='thinking'?'reasoning':'text'});}}actionTick++;pet.advanceEngine(++demoTick*1000/30,true,!demoDone&&actionChoice==='waiting');if(demoRunning&&demoTick>=180*workPhases.length)finishWork();previous=current;current=JSON.parse(pet.presentation(appearance,navigator.language));lastTickAt=received=performance.now();current.producerConnected=!demoDone&&actionChoice!=='unknown';current.epoch='preview';}
function readable(c){return c[0]*.2126+c[1]*.7152+c[2]*.0722>145?'#28383d':'#d4e5e5'}
function paint(c,f,a,now,small=false){const w=c.clientWidth,h=c.clientHeight,d=small?1:Math.min(devicePixelRatio||1,2);if(!w||!h)return;if(c.width!==Math.round(w*d)||c.height!==Math.round(h*d)){c.width=Math.round(w*d);c.height=Math.round(h*d)}const ctx=c.getContext('2d');ctx.setTransform(d,0,0,d,0,0);const gradient=ctx.createRadialGradient(w*.5,0,0,w*.5,h*.18,w*.9);gradient.addColorStop(0,hex(a.backgroundTop));gradient.addColorStop(1,hex(a.background));ctx.fillStyle=gradient;ctx.fillRect(0,0,w,h);if(!f)return;const still=$('still').checked,pose=still?f.still:f,state=pose.state,style=pose.style,stale=!preview&&(now-lastTickAt>800||now-received>800),t=still?0:f.timeMs/1000;
if(a.environment){ctx.strokeStyle=readable(a.background);ctx.globalAlpha=.12;ctx.lineWidth=.7;ctx.beginPath();ctx.moveTo(20,26);ctx.lineTo(w-20,26);ctx.stroke();for(let lane=0;lane<6;lane++){ctx.globalAlpha=.025;ctx.beginPath();for(let x=0;x<=w;x+=8){const y=h*.85+Math.sin(x/110+lane+t*.18)*7+lane*5;x?ctx.lineTo(x,y):ctx.moveTo(x,y)}ctx.stroke()}}ctx.globalAlpha=1;
@@ -77,10 +80,11 @@ const complete=demoDone&&!small,scale=Math.min(w*.44,h*(complete?.18:.34)),ox=w*
const light=a.background[0]*.2126+a.background[1]*.7152+a.background[2]*.0722>145;
const radius=Math.max(.6,Math.min(w,h)*.0022)*a.dotScale,hollow=stale||!f.producerConnected||state.observed<.92;
const fallback=[style.r,style.g,style.b,style.alpha],local=preview||small;
const pixel=pose.bodyStyle==='pixel',morph=pose.morph??1;if(pixel&&pixelWhale.complete&&pixelWhale.naturalWidth){ctx.globalAlpha=(1-morph)*Math.min(1,a.brightness)*(hollow?.45:1)*(stale?.55:1);ctx.imageSmoothingEnabled=false;ctx.filter=hollow?'grayscale(1)':'none';ctx.drawImage(pixelSource(a),ox-scale*.8341,oy-scale*.8341,scale*1.6682,scale*1.6682);ctx.filter='none';ctx.globalAlpha=1;}
for(let i=0;i<pose.points.length;i++){const q=pose.points[i],p=previous?.points[i],x=ox+(p&&!still?p[0]+(q[0]-p[0])*mix:q[0])*scale,y=oy+(p&&!still?p[1]+(q[1]-p[1])*mix:q[1])*scale,m=pose.materials?.[i]||fallback;
let color=m.slice(0,3),alpha=m[3];if(local){if(!a.eventColors)color=a.particle;else if(light){const peak=Math.max(...color,1);color=color.map(v=>Math.round(v*Math.min(1,115/peak)))}alpha=Math.min(1,alpha*a.brightness/(preview?1:f.appearance?.brightness||1))}if(hollow)alpha=Math.min(alpha,.48);if(stale)alpha*=.55;
let color=m.slice(0,3),alpha=m[3];if(local){if(!a.eventColors)color=a.particle;else if(light){const peak=Math.max(...color,1);color=color.map(v=>Math.round(v*Math.min(1,115/peak)))}alpha=Math.min(1,alpha*a.brightness/(preview?1:f.appearance?.brightness||1))}if(pixel&&pixelWhale.complete&&pixelWhale.naturalWidth)alpha*=morph;if(hollow)alpha=Math.min(alpha,.48);if(stale)alpha*=.55;
const material='rgb('+color.map(Math.round).join(',')+')',depth=.8+.2*Math.min(1,alpha);ctx.fillStyle=ctx.strokeStyle=material;ctx.globalAlpha=alpha;ctx.lineWidth=Math.max(.7,radius*.6);ctx.beginPath();ctx.arc(x,y,radius*depth,0,Math.PI*2);hollow?ctx.stroke():ctx.fill();if(!hollow&&!light&&a.glow>0&&i%5===0){ctx.globalAlpha=alpha*.035*a.glow;ctx.beginPath();ctx.arc(x,y,radius*(1+a.glow*4),0,Math.PI*2);ctx.fill()}}ctx.globalAlpha=1;}
function draw(now){requestAnimationFrame(draw);if(document.hidden)return;const dt=lastDraw?Math.min(.1,(now-lastDraw)/1000):0;lastDraw=now;if(preview&&pet){accumulator+=dt;let count=0;while(accumulator>=1/30&&count++<3){advancePreview();accumulator-=1/30}}if(document.body.classList.contains('gallery-mode')){if(++galleryTick%3===0)Array.from($('gallery').querySelectorAll('canvas')).forEach((c,i)=>paint(c,current,presets[i],now,true))}else paint(canvas,current,appearance,now);if(current){const p=$('still').checked?current.still:current,activity=current.activity,c=cue(activity),known=c&&(preview||now-lastTickAt<800),title=demoDone?'Settling back into the habitat.':known?c.label:'A moment between tasks.',detail=demoDone?'Preview complete':known?((preview?'Simulated action':'Observed action')+(c.actionId?' · '+c.actionId:'')):'Activity unobserved',evidence=preview?'Simulated preview':known?'Observed in Codewhale':'Local companion';for(const [id,text] of [['activity-title',title],['activity-detail',detail],['activity-evidence',evidence]])if($(id).textContent!==text)$(id).textContent=text;$('activity-parallel').textContent=known&&activity.parallelAgentCount?activity.parallelAgentCount+' parallel agent'+(activity.parallelAgentCount===1?'':'s')+' active':'';$('caption').textContent=(preview?'Preview · ':'')+'Codewhale'+(current.state.observed<.92?' · unobserved':'')+(demoDone?' · complete':'');$('clock').textContent=Math.floor(current.timeMs/60000)+':'+String(Math.floor(current.timeMs/1000)%60).padStart(2,'0');canvas.setAttribute('aria-label',title+'; '+detail+'; '+$('caption').textContent+'; '+(selected==='Custom'?'custom':selected)+' appearance')}}
function draw(now){requestAnimationFrame(draw);if(document.hidden)return;const dt=lastDraw?Math.min(.1,(now-lastDraw)/1000):0;lastDraw=now;if(preview&&pet){accumulator+=dt;let count=0;while(accumulator>=1/30&&count++<3){advancePreview();accumulator-=1/30}}if(document.body.classList.contains('gallery-mode')){if(++galleryTick%3===0)Array.from($('gallery').querySelectorAll('canvas')).forEach((c,i)=>paint(c,current,presets[i],now,true))}else paint(canvas,current,appearance,now);if(current){const p=$('still').checked?current.still:current,activity=current.activity,c=cue(activity),known=c&&(preview||now-lastTickAt<800),title=demoDone?'Settling back into the habitat.':known?c.label:'A moment between tasks.',detail=demoDone?'Preview complete':known?((preview?'Simulated action':'Observed action')+(c.actionId?' · '+c.actionId:'')):'Activity unobserved',evidence=preview?'Simulated preview':known?'Observed in Codewhale':'Local companion';for(const [id,text] of [['activity-title',title],['activity-detail',detail],['activity-evidence',evidence]])if($(id).textContent!==text)$(id).textContent=text;const phrase=$('activity-phrase');phrase.hidden=!known||!current.displayPhrase;phrase.lang=current.displayLanguage||'en';if(phrase.textContent!==(current.displayPhrase||''))phrase.textContent=current.displayPhrase||'';$('activity-parallel').textContent=known&&activity.parallelAgentCount?activity.parallelAgentCount+' parallel agent'+(activity.parallelAgentCount===1?'':'s')+' active':'';$('caption').textContent=(preview?'Preview · ':'')+'Codewhale'+(current.state.observed<.92?' · unobserved':'')+(demoDone?' · complete':'');$('clock').textContent=Math.floor(current.timeMs/60000)+':'+String(Math.floor(current.timeMs/1000)%60).padStart(2,'0');canvas.setAttribute('aria-label',title+'; '+detail+'; '+$('caption').textContent+'; '+(current.displayPhrase?current.displayPhrase+'; '+current.displayLanguage+'; ':'')+(selected==='Custom'?'custom':selected)+' appearance')}}
async function connect(){if(preview){await makePreview();return}const token=location.hash.slice(1);history.replaceState(null,'',location.pathname);if(token){if(!/^[a-f0-9]{64}$/i.test(token))throw Error('Invalid local connection');await request('/v1/attach',{}, {Authorization:'Bearer '+token})}$('join').hidden=true;$('message').textContent='One live pet. Appearance changes are shared; preview work stays isolated.';await poll()}
requestAnimationFrame(draw);connect().catch(e=>{$('message').textContent=e.message});
</script></html>
+80 -21
View File
@@ -1,5 +1,7 @@
import argparse
import hashlib
import json
import xml.etree.ElementTree as ET
from pathlib import Path
from PIL import Image, ImageDraw, ImageFont
@@ -8,8 +10,37 @@ parser = argparse.ArgumentParser()
parser.add_argument("font", type=Path, nargs="?")
parser.add_argument("--output", type=Path)
parser.add_argument("--whale-body", action="store_true")
parser.add_argument("--pixel-body", action="store_true")
args = parser.parse_args()
root = Path(__file__).resolve().parents[1]
if args.pixel_body:
source = root / "assets/pixel-whale.svg"
if hashlib.sha256(source.read_bytes()).hexdigest() != "9d3742fbf325aedc18435d7b15e89178b8684ccbb40cd55a32e60bed156de700":
raise ValueError("Expected the approved 192-cell Codewhale pixel whale")
step = 512 / 192
candidates = []
for rect in ET.fromstring(source.read_text()):
color = rect.attrib["fill"].lstrip("#")
rgb = tuple(int(color[i:i + 2], 16) for i in (0, 2, 4))
for col in range(max(1, round(float(rect.attrib["width"]) / step))):
candidates.append((float(rect.attrib["x"]) + (col + .5) * step,
float(rect.attrib["y"]) + step / 2, *rgb))
distances = [float("inf")] * len(candidates)
selected = []
at = 0
for _ in range(980):
x, y, *color = candidates[at]
selected.append((x, y, *color))
distances = [min(d, (x - p[0]) ** 2 + (y - p[1]) ** 2)
for d, p in zip(distances, candidates, strict=True)]
at = max(range(len(distances)), key=distances.__getitem__)
output = args.output or root / "src/core/dot-pixel-body.ts"
lines = ["export const DOT_PIXEL_BODY: readonly (readonly [number, number, number, number, number])[] = ["]
lines.extend(f" [{(x - 256) / 512 * .878:.6f}, {(y - 256) / 512 * .878:.6f}, {r}, {g}, {b}]," for x, y, r, g, b in selected)
lines.append("];")
output.write_text("\n".join(lines) + "\n", encoding="utf-8")
print(f"Generated 980 drones from the approved 192-cell pixel whale: {output}")
raise SystemExit(0)
if args.whale_body:
source = root / "public/whale.png"
if hashlib.sha256(source.read_bytes()).hexdigest() != "dc180fe5bde033f55bed5c7c7c1ab81182f31dd5913df237fb23e2a2da5098df":
@@ -38,32 +69,60 @@ if args.whale_body:
print(f"Generated 980 interior dots from the filled pet mark: {output}")
raise SystemExit(0)
if args.font is None:
parser.error("font is required unless --whale-body is selected")
parser.error("font is required unless --whale-body or --pixel-body is selected")
args.output = args.output or root / "src/core/dot-glyphs.ts"
font_hash = hashlib.sha256(args.font.read_bytes()).hexdigest()
if font_hash != "ca094f6b0001fb048ca39ddd797a0cdb0179e1e55c6561e111c49c3e6a61d7b7":
raise ValueError("Expected the pinned Noto Sans CJK SC Medium font in dot-glyphs-LICENSE.txt")
characters = dict(zip(
("reading", "editing", "searching", "testing", "executing", "browsing", "computer", "memory", "tool", "thinking", "responding", "delegating", "waiting", "done"),
("读", "写", "搜", "试", "行", "览", "控", "记", "工具", "思", "答", "协", "待", "成"),
strict=True,
))
lines = ["import type { OwnerActivityKind } from './pet-engine.js';", "", "export const DOT_GLYPHS: Record<OwnerActivityKind | 'waiting' | 'done', string[]> = {"]
messages = {
"reading": "ToolFamilyRead", "editing": "ToolFamilyPatch",
"searching": "ToolFamilyFind", "testing": "ToolFamilyVerify",
"executing": "ToolFamilyRun", "browsing": "PetActivityBrowsing",
"computer": "PetActivityComputer", "memory": "PetActivityMemory",
"tool": "ToolFamilyGeneric", "thinking": "ToolFamilyThink",
"responding": "PetModeReplyTitle", "delegating": "PhaseSubagents",
"waiting": "PhaseWaitingOnYou", "done": "PhaseDone",
}
languages = ["en", "zh-Hans", "zh-Hant", "ja"]
labels = {language: {key: json.loads((root.parent / f"crates/localization/locales/{language}.json").read_text(encoding="utf-8"))[message]
for key, message in messages.items()} for language in languages}
lines = ["import type { OwnerActivityKind } from './pet-engine.js';", "",
"export type GlyphLanguage = 'en' | 'zh-Hans' | 'zh-Hant' | 'ja';", "",
"export const DOT_LABELS = " + json.dumps(labels, ensure_ascii=False, indent=2) + " as const;", "",
"export const DOT_GLYPHS: Record<GlyphLanguage, Record<OwnerActivityKind | 'waiting' | 'done', string[]>> = {"]
sizes = set()
for key, char in characters.items():
for size in range(48, 0, -1):
font = ImageFont.truetype(str(args.font), size)
left, top, right, bottom = font.getbbox(char)
if right - left <= 44 and bottom - top <= 44:
break
sizes.add(size)
image = Image.new("L", (48, 48), 0)
ImageDraw.Draw(image).text(((48 - right + left) // 2 - left, (48 - bottom + top) // 2 - top), char, font=font, fill=255)
rows = ["".join("#" if image.getpixel((x, y)) >= 128 else "." for x in range(48)) for y in range(48)]
lines.append(f" {key}: [")
lines.extend(f" '{row}'," for row in rows)
lines.append(" ],")
for language, characters in labels.items():
lines.append(f" '{language}': {{")
for key, char in characters.items():
split = len(char) // 2
words = char.split() if language == "en" else [char] if len(char) <= 3 else [char[:split], char[split:]]
if language == "ja" and "を" in char:
at = char.index("を")
words = [char[:at], char[at:]]
elif language == "ja" and "エージェント" in char:
at = char.index("エージェント")
end = at + len("エージェント")
words = [word for word in [char[:at], char[at:end], char[end:]] if word]
for size in range(48, 0, -1):
font = ImageFont.truetype(str(args.font), size)
boxes = [font.getbbox(word) for word in words]
line_height = max(bottom - top for left, top, right, bottom in boxes)
height = line_height * len(words) + 2 * (len(words) - 1)
if max(right - left for left, top, right, bottom in boxes) <= 44 and height <= 44:
break
sizes.add(size)
image = Image.new("L", (48, 48), 0)
draw = ImageDraw.Draw(image)
y = (48 - height) // 2
for word, (left, top, right, bottom) in zip(words, boxes, strict=True):
draw.text(((48 - right + left) // 2 - left, y - top), word, font=font, fill=255)
y += line_height + 2
rows = ["".join("#" if image.getpixel((x, y)) >= 128 else "." for x in range(48)) for y in range(48)]
lines.append(f" {key}: [")
lines.extend(f" '{row}'," for row in rows)
lines.append(" ],")
lines.append(" },")
lines.append("};")
args.output.parent.mkdir(parents=True, exist_ok=True)
args.output.write_text("\n".join(lines) + "\n", encoding="utf-8")
print(f"Generated {len(characters)} masks at 48 × 48, font sizes {sorted(sizes)}, threshold 128: {args.output}")
print(f"Generated {len(languages) * len(messages)} masks at 48 × 48, font sizes {sorted(sizes)}, threshold 128: {args.output}")
+4 -3
View File
@@ -8,7 +8,7 @@ import ts from 'typescript';
const root = fileURLToPath(new URL('../', import.meta.url));
const result = spawnSync(process.execPath, [root + 'node_modules/typescript/bin/tsc', '-p', root + 'tsconfig.json'], { stdio: 'inherit' });
if (result.status !== 0) process.exit(result.status || 1);
for (const name of ['pet.html', 'shared.html', 'pet.css', 'whale-points.tsv']) await cp(root + 'public/' + name, root + 'dist/' + name);
for (const name of ['pet.html', 'shared.html', 'pet.css', 'whale-points.tsv', 'pixel-whale.webp']) await cp(root + 'public/' + name, root + 'dist/' + name);
const modules = new Map();
async function moduleFor(name) {
if (modules.has(name)) return;
@@ -32,12 +32,13 @@ const generated = [];
if (check || process.argv.includes('--tui')) {
const destination = root + '../crates/tui/src/tui/pet_watch/';
generated.push([destination + 'pet-native.js', Buffer.from(bundle)],
[destination + 'shared.html', await readFile(root + 'public/shared.html')]);
[destination + 'shared.html', await readFile(root + 'public/shared.html')],
[destination + 'pixel-whale.webp', await readFile(root + 'public/pixel-whale.webp')]);
}
if (process.argv.includes('--study')) {
const study = root + 'world/';
await mkdir(study, { recursive: true });
for (const name of ['pet.html', 'pet.css', 'whale-points.tsv', 'core', 'ui/pet.js', 'ui/storage.js']) {
for (const name of ['pet.html', 'pet.css', 'whale-points.tsv', 'pixel-whale.webp', 'core', 'ui/pet.js', 'ui/storage.js']) {
await mkdir(study + name.split('/').slice(0, -1).join('/'), { recursive: true });
await cp(root + 'dist/' + name, study + name, { recursive: true });
}
+30 -12
View File
@@ -1,16 +1,22 @@
import { stableHash } from './model.js';
import { DOT_GLYPHS } from './dot-glyphs.js';
import { DOT_GLYPHS, type GlyphLanguage } from './dot-glyphs.js';
import { DOT_BODY } from './dot-body.js';
import { DOT_PIXEL_BODY } from './dot-pixel-body.js';
import type { EngineOwnerProjection, OwnerActivityKind } from './pet-engine.js';
export interface DotField { points: number[][]; materials: number[][] }
export type BodyStyle = 'smooth' | 'pixel';
export type TextLanguage = GlyphLanguage | 'auto';
export interface DotOptions { bodyStyle?: BodyStyle; textLanguage?: TextLanguage }
export interface DotField { points: number[][]; materials: number[][]; morph: number; bodyStyle: BodyStyle }
type Verb = OwnerActivityKind | 'waiting' | 'done';
const TAU = Math.PI * 2;
const clamp = (n: number, lo = 0, hi = 1) => Math.max(lo, Math.min(hi, n));
const ease = (n: number) => { const u = clamp(n); return u * u * (3 - 2 * u); };
const GLYPH_POINTS = Object.fromEntries(Object.entries(DOT_GLYPHS).map(([key, rows]) => [key,
rows.flatMap((row, y) => [...row].flatMap((pixel, x) => pixel === '#' ? [[(x - 23.5) / 24, (y - 23.5) / 24]] : [])),
])) as Record<Verb, number[][]>;
const GLYPH_POINTS = Object.fromEntries(Object.entries(DOT_GLYPHS).map(([language, glyphs]) => [language,
Object.fromEntries(Object.entries(glyphs).map(([key, rows]) => [key,
rows.flatMap((row, y) => [...row].flatMap((pixel, x) => pixel === '#' ? [[(x - 23.5) / 24, (y - 23.5) / 24]] : [])),
])),
])) as Record<GlyphLanguage, Record<Verb, number[][]>>;
const HUES: Record<Verb, number> = {
reading: 185, editing: 190, searching: 194, testing: 201, executing: 198,
browsing: 195, computer: 198, memory: 180, tool: 195, thinking: 185,
@@ -38,13 +44,21 @@ function pigment(hue: number, light: number): number[] {
return rgb.map(v => Math.round((v + m) * 255));
}
export function textLanguage(value = 'auto', interfaceLanguage = 'en'): GlyphLanguage {
const tag = (value === 'auto' ? interfaceLanguage : value).toLowerCase();
return tag.startsWith('ja') ? 'ja' : /^(zh-hant|zh-tw|zh-hk|zh-mo)/.test(tag) ? 'zh-Hant'
: tag.startsWith('zh') ? 'zh-Hans' : 'en';
}
export function projectDotField(activity: EngineOwnerProjection,
timeMs: number, still = false, expressionAtMs = 0): DotField {
timeMs: number, still = false, expressionAtMs = 0, options: DotOptions = {}): DotField {
const kind = verb(activity, timeMs), identity = activity.actionId || activity.activityKind || 'whale';
const seed = stableHash(identity), variant = (seed % 997) / 997;
const t = still ? 0 : timeMs / 1000, cycle = still ? .43 : (t / (2.8 + variant * .6)) % 1;
const sweep = -.86 + cycle * 1.72, scanRow = Math.floor(cycle * 5) % 5;
const glyph = kind ? GLYPH_POINTS[kind] : null;
const bodyStyle = options.bodyStyle ?? 'smooth', language = textLanguage(options.textLanguage);
const body = bodyStyle === 'pixel' ? DOT_PIXEL_BODY : DOT_BODY;
const glyph = kind ? GLYPH_POINTS[language][kind] : null;
const fresh = activity.freshness === 'fresh' && activity.observed;
const failed = fresh && !activity.activeSpans.length && !!activity.failedToolAge;
const phase = ((Math.max(0, timeMs - expressionAtMs) / 1000 + 1.7) % 8.4);
@@ -53,8 +67,8 @@ export function projectDotField(activity: EngineOwnerProjection,
: ease(phase / 1.7) * (1 - ease((phase - 5.4) / .9));
const points: number[][] = [], materials: number[][] = [];
const lens = [Math.sin(cycle * TAU) * .62, -.62 + scanRow * .31];
for (let i = 0; i < DOT_BODY.length; i++) {
const [hx, hy] = DOT_BODY[i];
for (let i = 0; i < body.length; i++) {
const [hx, hy] = body[i];
const tail = clamp((-hx - hy + .1) / .65);
const homeX = hx * 1.9 + (still ? 0 : .008 * Math.sin(t * .75 + hy * 5));
const homeY = hy * 1.9 + (still ? 0 : .009 * Math.sin(t * 1.1 + hx * 4)
@@ -62,7 +76,7 @@ export function projectDotField(activity: EngineOwnerProjection,
const bodyLight = .61 + .14 * clamp((hx + .45) / .9) + .035 * Math.cos(hy * 7);
let x = homeX, y = homeY, light = bodyLight, alpha = fresh ? .88 : .42;
if (glyph && kind) {
[x, y] = glyph[Math.floor(i * glyph.length / DOT_BODY.length)];
[x, y] = glyph[Math.floor(i * glyph.length / body.length)];
x += ((i * 17) % 7 - 3) * .0016;
y += ((i * 11) % 7 - 3) * .0016;
let beam = Math.exp(-Math.pow((y - sweep) * 10, 2));
@@ -101,17 +115,21 @@ export function projectDotField(activity: EngineOwnerProjection,
if (!still) light = clamp(light + .025 * Math.sin(hx * 10 - hy * 5 - t * 1.1), .5, .9);
points.push([clamp(x, -.94, .94), clamp(y, -.94, .94)]);
const hue = morph > 0 ? (failed ? 16 : kind ? HUES[kind] : 188) : 188;
const base = pigment(188 + variant * 6 - 3, light), accent = pigment(hue + variant * 6 - 3, light);
const base = bodyStyle === 'pixel' ? DOT_PIXEL_BODY[i].slice(2)
: pigment(185 + clamp((hy - hx + .45) / .9) * 70, light);
const accent = pigment(hue + variant * 6 - 3, light);
const color = !fresh ? [145, 166, 178] : base.map((v, k) => Math.round(v + (accent[k] - v) * morph));
materials.push([...color, alpha]);
}
return { points, materials };
return { points, materials, morph, bodyStyle };
}
export function blendDotFields(from: DotField, to: DotField, progress: number): DotField {
const mix = ease(progress);
if (mix >= 1) return to;
return {
morph: from.morph + (to.morph - from.morph) * mix,
bodyStyle: to.bodyStyle,
points: to.points.map((p, i) => p.map((v, k) => from.points[i][k] + (v - from.points[i][k]) * mix)),
materials: to.materials.map((p, i) => p.map((v, k) => from.materials[i][k] + (v - from.materials[i][k]) * mix)),
};
+2878 -701
View File
File diff suppressed because it is too large. Load diff
+982
View File
@@ -0,0 +1,982 @@
export const DOT_PIXEL_BODY: readonly (readonly [number, number, number, number, number])[] = [
[-0.070881, -0.368120, 0, 215, 232],
[0.057161, 0.363547, 11, 111, 255],
[-0.404703, 0.066307, 56, 65, 255],
[0.395557, -0.052589, 0, 215, 232],
[0.034297, -0.002287, 11, 111, 255],
[-0.331536, -0.221787, 11, 111, 255],
[0.308672, 0.221786, 3, 167, 255],
[-0.221787, 0.285807, 56, 65, 255],
[0.185204, -0.240078, 41, 247, 187],
[-0.180631, 0.066307, 56, 65, 255],
[0.116610, 0.176057, 11, 111, 255],
[0.221786, 0.020579, 3, 167, 255],
[-0.061735, 0.203494, 56, 65, 255],
[0.006860, -0.208068, 0, 215, 232],
[-0.294953, -0.057162, 56, 65, 255],
[-0.166911, -0.230932, 3, 167, 255],
[-0.093745, 0.363547, 76, 25, 214],
[0.390985, 0.098318, 3, 167, 255],
[-0.345256, 0.203494, 76, 25, 214],
[0.194348, 0.313244, 11, 111, 255],
[0.057161, -0.336109, 0, 215, 232],
[-0.057161, 0.080025, 11, 111, 255],
[-0.390984, -0.121182, 11, 111, 255],
[-0.230932, 0.171485, 56, 65, 255],
[-0.253796, -0.304100, 3, 167, 255],
[-0.294953, 0.080025, 56, 65, 255],
[0.038869, 0.253797, 11, 111, 255],
[0.121182, 0.066307, 11, 111, 255],
[0.290380, -0.061734, 0, 215, 232],
[0.249224, 0.134900, 3, 167, 255],
[-0.249224, -0.162339, 11, 111, 255],
[0.134902, -0.034296, 3, 167, 255],
[0.020578, 0.144047, 11, 111, 255],
[0.102890, -0.185203, 0, 215, 232],
[-0.075453, -0.267515, 3, 167, 255],
[0.322391, 0.029724, 3, 167, 255],
[-0.162339, -0.336109, 3, 167, 255],
[-0.125756, 0.272088, 56, 65, 255],
[-0.409276, -0.029725, 56, 65, 255],
[-0.139475, 0.153193, 56, 65, 255],
[-0.038870, 0.294954, 56, 65, 255],
[0.221786, 0.230932, 11, 111, 255],
[0.112037, 0.299525, 11, 111, 255],
[0.102891, -0.267515, 0, 215, 232],
[-0.336110, 0.011432, 56, 65, 255],
[-0.166912, 0.340682, 56, 65, 255],
[0.413850, 0.025150, 0, 215, 232],
[-0.363546, 0.130329, 76, 25, 214],
[-0.002287, -0.290381, 0, 215, 232],
[-0.294953, 0.258369, 76, 25, 214],
[0.176057, 0.116610, 3, 167, 255],
[0.340682, 0.153193, 0, 215, 232],
[0.208068, -0.052589, 0, 215, 232],
[-0.317818, -0.134901, 56, 65, 255],
[0.043443, 0.070880, 11, 111, 255],
[-0.016006, 0.368119, 76, 25, 214],
[-0.171485, 0.217213, 56, 65, 255],
[-0.240078, -0.235506, 3, 167, 255],
[-0.294953, 0.148619, 56, 65, 255],
[0.148619, 0.235505, 3, 167, 255],
[-0.002286, -0.363546, 0, 215, 232],
[-0.235506, 0.107463, 11, 111, 255],
[-0.025151, 0.020579, 11, 111, 255],
[-0.121183, 0.089172, 11, 111, 255],
[0.084599, 0.121182, 11, 111, 255],
[-0.075453, 0.139474, 11, 111, 255],
[0.057161, 0.194349, 11, 111, 255],
[-0.358975, -0.066307, 56, 65, 255],
[0.093745, 0.011432, 3, 167, 255],
[-0.002286, 0.208068, 11, 111, 255],
[-0.134900, -0.281234, 3, 167, 255],
[-0.194348, -0.285807, 3, 167, 255],
[0.340682, -0.029725, 0, 215, 232],
[0.162338, 0.020579, 3, 167, 255],
[0.226359, 0.080025, 3, 167, 255],
[0.267516, 0.267516, 11, 111, 255],
[0.043443, -0.253796, 0, 215, 232],
[0.016005, 0.313244, 56, 65, 255],
[-0.276662, 0.025150, 56, 65, 255],
[-0.240078, 0.230932, 56, 65, 255],
[0.272088, -0.006859, 0, 215, 232],
[-0.363546, -0.171484, 56, 65, 255],
[0.262943, 0.189775, 3, 167, 255],
[-0.116609, 0.212641, 56, 65, 255],
[-0.294953, -0.267515, 11, 111, 255],
[-0.349828, 0.070880, 56, 65, 255],
[-0.290381, 0.203494, 76, 25, 214],
[-0.093745, -0.317818, 3, 167, 255],
[0.272089, 0.048016, 3, 167, 255],
[0.322391, 0.102891, 3, 167, 255],
[-0.189776, 0.134900, 11, 111, 255],
[-0.006860, 0.098318, 11, 111, 255],
[0.134901, -0.226359, 0, 215, 232],
[-0.281234, -0.203495, 3, 167, 255],
[-0.089172, 0.308672, 56, 65, 255],
[-0.038870, -0.326964, 0, 215, 232],
[0.148620, 0.336110, 11, 111, 255],
[0.212641, 0.180630, 3, 167, 255],
[-0.029724, 0.162338, 11, 111, 255],
[-0.029725, -0.244651, 0, 215, 232],
[-0.386411, 0.016005, 56, 65, 255],
[0.052588, -0.185203, 3, 167, 255],
[0.084600, 0.235505, 11, 111, 255],
[-0.080026, 0.249224, 56, 65, 255],
[-0.176057, 0.267516, 56, 65, 255],
[-0.240078, 0.057161, 56, 65, 255],
[-0.093745, 0.048016, 11, 111, 255],
[0.180630, 0.066307, 3, 167, 255],
[0.368120, 0.011432, 0, 215, 232],
[0.102890, 0.349829, 56, 65, 255],
[-0.121182, -0.358975, 3, 167, 255],
[-0.025151, 0.249224, 56, 65, 255],
[0.130329, 0.130329, 11, 111, 255],
[0.157766, 0.281235, 11, 111, 255],
[-0.208068, -0.331537, 3, 167, 255],
[0.294954, 0.148619, 3, 167, 255],
[-0.281234, -0.107464, 56, 65, 255],
[-0.052589, 0.340682, 56, 65, 255],
[0.080025, -0.226359, 0, 215, 232],
[0.221786, 0.276661, 11, 111, 255],
[-0.208068, -0.203495, 3, 167, 255],
[0.176057, -0.020578, 0, 215, 232],
[-0.317817, -0.180631, 11, 111, 255],
[-0.299526, -0.011432, 11, 111, 255],
[-0.326964, 0.107463, 56, 65, 255],
[0.016005, 0.038869, 3, 167, 255],
[0.180630, 0.208068, 11, 111, 255],
[-0.400131, -0.080026, 56, 65, 255],
[-0.130328, 0.317818, 56, 65, 255],
[-0.326964, -0.093745, 11, 111, 255],
[0.249224, -0.043443, 3, 167, 255],
[-0.180631, 0.176057, 56, 65, 255],
[-0.336109, 0.162338, 56, 65, 255],
[-0.363546, -0.020578, 11, 111, 255],
[0.386411, 0.057161, 3, 167, 255],
[-0.162338, 0.102891, 56, 65, 255],
[-0.102890, 0.171485, 56, 65, 255],
[0.080027, 0.052588, 3, 167, 255],
[0.016005, -0.326964, 0, 215, 232],
[0.413848, -0.016006, 0, 215, 232],
[-0.313244, 0.043443, 56, 65, 255],
[0.043443, 0.112036, 11, 111, 255],
[0.189775, 0.253797, 11, 111, 255],
[-0.038870, 0.121182, 11, 111, 255],
[-0.112036, -0.249225, 3, 167, 255],
[0.080025, 0.162338, 11, 111, 255],
[-0.258370, 0.272088, 76, 25, 214],
[-0.020578, 0.061735, 11, 111, 255],
[-0.390984, 0.102891, 76, 25, 214],
[0.336110, 0.194349, 3, 167, 255],
[-0.208068, 0.203494, 76, 25, 214],
[0.002286, 0.276661, 56, 65, 255],
[0.020578, 0.354400, 56, 65, 255],
[-0.203495, -0.249225, 3, 167, 255],
[-0.112037, 0.125755, 56, 65, 255],
[-0.258370, 0.139474, 56, 65, 255],
[-0.038870, -0.281234, 0, 215, 232],
[-0.285807, -0.153193, 11, 111, 255],
[-0.203495, 0.240079, 56, 65, 255],
[0.217213, -0.016006, 0, 215, 232],
[0.372692, 0.130329, 3, 167, 255],
[-0.326964, -0.038870, 11, 111, 255],
[-0.198921, 0.098318, 11, 111, 255],
[0.308672, -0.011432, 0, 215, 232],
[0.125755, 0.029724, 3, 167, 255],
[-0.354401, -0.125756, 56, 65, 255],
[0.006860, -0.249225, 3, 167, 255],
[-0.235506, -0.272089, 11, 111, 255],
[0.358974, -0.061734, 0, 215, 232],
[-0.057161, 0.038869, 11, 111, 255],
[-0.244650, -0.198921, 11, 111, 255],
[-0.130329, -0.317818, 3, 167, 255],
[-0.016005, 0.331536, 56, 65, 255],
[-0.130328, 0.354400, 76, 25, 214],
[-0.144046, 0.240079, 76, 25, 214],
[0.148620, 0.089172, 3, 167, 255],
[-0.144047, 0.189775, 56, 65, 255],
[-0.267515, 0.176057, 56, 65, 255],
[-0.317818, 0.226360, 56, 65, 255],
[0.052588, 0.029724, 3, 167, 255],
[0.070880, -0.016006, 3, 167, 255],
[0.020579, 0.180630, 11, 111, 255],
[-0.084600, 0.102891, 11, 111, 255],
[-0.144047, 0.061735, 11, 111, 255],
[-0.272089, 0.107463, 56, 65, 255],
[0.116610, 0.249224, 11, 111, 255],
[-0.368120, 0.171485, 76, 25, 214],
[-0.358975, 0.038869, 56, 65, 255],
[0.089172, 0.084599, 3, 167, 255],
[0.043443, -0.217214, 0, 215, 232],
[0.029724, 0.221786, 56, 65, 255],
[0.276661, 0.230932, 11, 111, 255],
[0.070881, -0.276662, 0, 215, 232],
[0.093744, 0.203494, 11, 111, 255],
[0.102891, -0.025151, 3, 167, 255],
[0.377266, -0.020578, 0, 215, 232],
[-0.162339, -0.299526, 3, 167, 255],
[-0.299526, -0.230932, 3, 167, 255],
[-0.066307, -0.299526, 0, 215, 232],
[-0.070880, 0.281235, 56, 65, 255],
[0.290380, 0.020579, 0, 215, 232],
[-0.034297, -0.368120, 0, 215, 232],
[-0.166912, -0.267515, 3, 167, 255],
[0.322391, -0.057162, 0, 215, 232],
[-0.221786, 0.139474, 56, 65, 255],
[0.006860, 0.244650, 56, 65, 255],
[0.038869, 0.290380, 11, 111, 255],
[-0.272089, 0.235505, 56, 65, 255],
[0.130328, -0.002287, 3, 167, 255],
[-0.102891, -0.285807, 3, 167, 255],
[-0.066307, 0.171485, 56, 65, 255],
[0.139473, -0.194350, 0, 215, 232],
[0.253797, 0.020579, 0, 215, 232],
[0.011432, 0.070880, 3, 167, 255],
[-0.272088, 0.057161, 56, 65, 255],
[0.048016, 0.162338, 11, 111, 255],
[-0.034296, 0.217213, 11, 111, 255],
[0.235506, 0.048016, 3, 167, 255],
[-0.272088, -0.244651, 11, 111, 255],
[-0.011432, 0.134900, 11, 111, 255],
[0.198921, 0.093744, 3, 167, 255],
[-0.368120, -0.098318, 56, 65, 255],
[-0.112036, 0.244650, 56, 65, 255],
[0.194349, 0.038869, 3, 167, 255],
[0.189776, 0.006860, 3, 167, 255],
[-0.221787, -0.299526, 3, 167, 255],
[0.336110, 0.002286, 0, 215, 232],
[0.148619, 0.052588, 3, 167, 255],
[-0.212640, 0.070880, 56, 65, 255],
[0.116610, 0.098318, 11, 111, 255],
[0.144047, 0.157766, 3, 167, 255],
[-0.253796, 0.203494, 76, 25, 214],
[-0.194350, 0.326963, 76, 25, 214],
[0.070880, 0.336110, 56, 65, 255],
[-0.308671, 0.176057, 76, 25, 214],
[0.029723, -0.354401, 0, 215, 232],
[-0.386411, -0.052589, 56, 65, 255],
[-0.349828, -0.198921, 11, 111, 255],
[0.025152, -0.276662, 0, 215, 232],
[0.153192, -0.249225, 0, 215, 232],
[-0.409276, 0.034297, 56, 65, 255],
[0.102891, 0.144047, 3, 167, 255],
[0.162339, -0.212640, 0, 215, 232],
[-0.144047, -0.249225, 3, 167, 255],
[-0.107463, 0.336110, 56, 65, 255],
[-0.070881, -0.336109, 3, 167, 255],
[-0.253797, 0.084599, 56, 65, 255],
[-0.336109, -0.157765, 11, 111, 255],
[0.230932, 0.157766, 11, 111, 255],
[0.244650, 0.212641, 3, 167, 255],
[-0.153193, 0.285807, 76, 25, 214],
[-0.267515, -0.276662, 11, 111, 255],
[0.107463, -0.235506, 0, 215, 232],
[0.006860, 0.006860, 11, 111, 255],
[-0.358975, 0.098318, 76, 25, 214],
[0.281235, -0.034296, 0, 215, 232],
[0.121182, 0.326963, 11, 111, 255],
[-0.230932, 0.258369, 56, 65, 255],
[-0.089171, 0.221786, 56, 65, 255],
[-0.011432, -0.317818, 0, 215, 232],
[-0.061734, 0.368119, 76, 25, 214],
[-0.299525, 0.116610, 56, 65, 255],
[-0.052588, 0.258369, 56, 65, 255],
[-0.194348, 0.290380, 56, 65, 255],
[0.057161, 0.230932, 56, 65, 255],
[-0.322390, 0.134900, 76, 25, 214],
[-0.299526, -0.084600, 11, 111, 255],
[-0.322390, -0.066307, 56, 65, 255],
[0.244650, -0.011432, 3, 167, 255],
[-0.139475, 0.121182, 11, 111, 255],
[-0.304100, 0.016005, 56, 65, 255],
[-0.025151, 0.189775, 11, 111, 255],
[0.162339, 0.308672, 11, 111, 255],
[0.016005, 0.116610, 11, 111, 255],
[-0.011432, 0.304099, 56, 65, 255],
[0.066307, 0.258369, 56, 65, 255],
[-0.061734, 0.313244, 56, 65, 255],
[-0.377265, 0.061735, 56, 65, 255],
[-0.089171, 0.075454, 56, 65, 255],
[-0.322390, 0.075454, 56, 65, 255],
[-0.409276, -0.002287, 56, 65, 255],
[-0.002286, 0.162338, 11, 111, 255],
[0.308672, 0.194349, 3, 167, 255],
[-0.080025, 0.336110, 56, 65, 255],
[0.075454, -0.198921, 3, 167, 255],
[-0.116609, 0.061735, 11, 111, 255],
[-0.034296, 0.093744, 56, 65, 255],
[-0.203495, 0.162338, 56, 65, 255],
[-0.057161, 0.230932, 56, 65, 255],
[-0.098318, -0.345256, 3, 167, 255],
[-0.166911, 0.148619, 56, 65, 255],
[0.043443, 0.340682, 11, 111, 255],
[-0.185204, -0.317818, 3, 167, 255],
[0.038869, -0.313245, 0, 215, 232],
[0.066307, 0.098318, 3, 167, 255],
[-0.377265, -0.144046, 11, 111, 255],
[-0.016006, -0.267515, 0, 215, 232],
[-0.272088, -0.176057, 11, 111, 255],
[-0.089172, 0.194349, 56, 65, 255],
[0.125756, -0.253796, 0, 215, 232],
[0.395557, 0.002286, 0, 215, 232],
[0.208067, 0.061735, 3, 167, 255],
[0.281235, 0.171485, 3, 167, 255],
[-0.267516, -0.134901, 11, 111, 255],
[0.116610, -0.208068, 0, 215, 232],
[-0.020578, -0.345256, 0, 215, 232],
[0.157766, 0.134900, 3, 167, 255],
[-0.057162, -0.249225, 3, 167, 255],
[-0.317818, 0.198922, 56, 65, 255],
[0.244650, 0.258369, 11, 111, 255],
[-0.048016, 0.144047, 11, 111, 255],
[-0.194350, -0.226359, 3, 167, 255],
[0.070880, -0.249225, 0, 215, 232],
[0.166911, -0.043443, 3, 167, 255],
[0.025150, 0.093744, 11, 111, 255],
[0.029724, -0.194350, 0, 215, 232],
[-0.157765, 0.317818, 76, 25, 214],
[0.061735, 0.006860, 11, 111, 255],
[0.313244, 0.125755, 0, 215, 232],
[-0.112036, 0.299525, 56, 65, 255],
[-0.363547, 0.006860, 56, 65, 255],
[-0.386411, -0.011432, 56, 65, 255],
[0.102891, 0.043443, 3, 167, 255],
[-0.203495, 0.267516, 56, 65, 255],
[-0.308672, -0.208068, 11, 111, 255],
[-0.308672, -0.112037, 11, 111, 255],
[-0.326964, -0.011432, 56, 65, 255],
[0.134901, 0.290380, 11, 111, 255],
[-0.061734, 0.112036, 56, 65, 255],
[-0.262943, -0.221787, 3, 167, 255],
[0.153193, -0.011432, 3, 167, 255],
[0.409276, 0.048016, 0, 215, 232],
[0.368119, 0.102891, 3, 167, 255],
[-0.171484, 0.244650, 56, 65, 255],
[-0.098318, 0.148619, 56, 65, 255],
[-0.148619, 0.212641, 56, 65, 255],
[-0.102890, 0.267516, 56, 65, 255],
[-0.308672, -0.157765, 11, 111, 255],
[0.390985, 0.029724, 0, 215, 232],
[0.089172, 0.304099, 11, 111, 255],
[-0.144047, -0.354401, 3, 167, 255],
[-0.290381, -0.130328, 11, 111, 255],
[-0.336109, 0.048016, 56, 65, 255],
[-0.294953, 0.230932, 76, 25, 214],
[0.171485, 0.093744, 3, 167, 255],
[-0.354400, -0.043443, 56, 65, 255],
[0.043442, 0.139474, 11, 111, 255],
[-0.148620, 0.262943, 56, 65, 255],
[-0.029725, 0.272088, 56, 65, 255],
[0.171485, 0.043443, 3, 167, 255],
[0.198922, 0.290380, 11, 111, 255],
[0.080025, 0.358974, 11, 111, 255],
[-0.043443, -0.304100, 0, 215, 232],
[-0.230932, 0.208068, 56, 65, 255],
[0.153193, 0.112036, 3, 167, 255],
[0.313244, -0.034296, 3, 167, 255],
[-0.304098, -0.034296, 56, 65, 255],
[-0.386412, 0.125755, 76, 25, 214],
[-0.038869, 0.363547, 56, 65, 255],
[-0.294954, -0.185203, 11, 111, 255],
[-0.212641, 0.116610, 56, 65, 255],
[-0.070881, 0.057161, 56, 65, 255],
[0.075454, 0.029724, 11, 111, 255],
[0.363547, 0.153193, 3, 167, 255],
[0.244650, 0.281235, 11, 111, 255],
[-0.121183, 0.185204, 56, 65, 255],
[-0.034297, 0.043443, 11, 111, 255],
[0.363546, -0.038870, 0, 215, 232],
[-0.166911, 0.194349, 56, 65, 255],
[-0.052589, -0.349828, 0, 215, 232],
[-0.313245, -0.249225, 11, 111, 255],
[-0.011432, -0.230932, 3, 167, 255],
[0.020579, -0.230932, 0, 215, 232],
[-0.006859, 0.034297, 3, 167, 255],
[-0.162339, 0.080025, 11, 111, 255],
[-0.034297, 0.317818, 56, 65, 255],
[-0.331537, -0.116609, 11, 111, 255],
[0.230932, -0.057162, 0, 215, 232],
[0.267516, -0.057162, 0, 215, 232],
[0.189775, -0.038870, 3, 167, 255],
[0.038869, 0.048016, 11, 111, 255],
[0.400130, 0.075454, 0, 215, 232],
[0.377266, 0.080025, 0, 215, 232],
[0.326963, 0.171485, 3, 167, 255],
[-0.253796, 0.249224, 76, 25, 214],
[-0.011432, 0.230932, 11, 111, 255],
[-0.217214, -0.230932, 3, 167, 255],
[-0.230932, 0.084599, 56, 65, 255],
[-0.212640, -0.272089, 3, 167, 255],
[0.048016, -0.276662, 0, 215, 232],
[0.253797, 0.061735, 3, 167, 255],
[-0.048015, 0.185204, 11, 111, 255],
[0.349828, 0.176057, 3, 167, 255],
[-0.290380, 0.043443, 56, 65, 255],
[0.034296, 0.198922, 11, 111, 255],
[0.354401, -0.011432, 0, 215, 232],
[-0.157766, 0.171485, 56, 65, 255],
[-0.002287, 0.185204, 11, 111, 255],
[-0.089172, -0.249225, 3, 167, 255],
[-0.185203, -0.340682, 3, 167, 255],
[-0.386412, 0.038869, 56, 65, 255],
[0.066307, 0.075454, 11, 111, 255],
[-0.166912, 0.125755, 56, 65, 255],
[0.016005, -0.304100, 0, 215, 232],
[0.107463, 0.121182, 3, 167, 255],
[0.093744, 0.180630, 3, 167, 255],
[0.221786, 0.299525, 11, 111, 255],
[0.029724, 0.020579, 11, 111, 255],
[-0.281234, 0.130329, 76, 25, 214],
[-0.377265, 0.148619, 76, 25, 214],
[0.070880, 0.212641, 11, 111, 255],
[0.025150, 0.272088, 56, 65, 255],
[-0.144046, 0.336110, 56, 65, 255],
[-0.002287, 0.349829, 56, 65, 255],
[-0.253796, -0.258370, 3, 167, 255],
[-0.377265, 0.084599, 56, 65, 255],
[-0.180630, -0.249225, 3, 167, 255],
[-0.349828, -0.089171, 11, 111, 255],
[-0.249225, 0.162338, 56, 65, 255],
[-0.226359, -0.253796, 11, 111, 255],
[-0.390984, -0.098318, 56, 65, 255],
[0.230932, -0.034296, 3, 167, 255],
[-0.144046, 0.089172, 11, 111, 255],
[-0.345256, 0.180630, 56, 65, 255],
[-0.272089, 0.212641, 76, 25, 214],
[0.308672, 0.011432, 0, 215, 232],
[0.294954, 0.240079, 11, 111, 255],
[-0.340683, 0.125755, 56, 65, 255],
[-0.121182, -0.299526, 3, 167, 255],
[-0.340682, 0.089172, 56, 65, 255],
[0.349829, 0.020579, 3, 167, 255],
[0.176058, 0.272088, 11, 111, 255],
[0.093744, 0.331536, 56, 65, 255],
[-0.043443, 0.061735, 11, 111, 255],
[0.061735, 0.121182, 11, 111, 255],
[-0.308672, 0.098318, 56, 65, 255],
[-0.354401, 0.153193, 56, 65, 255],
[-0.121182, 0.162338, 56, 65, 255],
[-0.230932, -0.317818, 3, 167, 255],
[0.098318, 0.066307, 3, 167, 255],
[0.157766, 0.070880, 3, 167, 255],
[0.198922, 0.198922, 11, 111, 255],
[0.212641, 0.249224, 11, 111, 255],
[0.098318, -0.217214, 0, 215, 232],
[0.262943, -0.025151, 0, 215, 232],
[-0.285807, 0.166911, 56, 65, 255],
[-0.189776, 0.194349, 56, 65, 255],
[-0.194350, 0.221786, 56, 65, 255],
[0.395557, -0.029725, 41, 247, 187],
[-0.377265, -0.080026, 11, 111, 255],
[-0.116609, -0.336109, 3, 167, 255],
[-0.226359, -0.189776, 11, 111, 255],
[-0.203494, -0.308671, 3, 167, 255],
[-0.253796, 0.116610, 56, 65, 255],
[-0.326964, -0.198921, 56, 65, 255],
[0.006860, -0.345256, 0, 215, 232],
[-0.212641, 0.180630, 56, 65, 255],
[0.139473, 0.317818, 11, 111, 255],
[-0.354400, -0.148620, 11, 111, 255],
[0.048015, 0.272088, 11, 111, 255],
[-0.340682, -0.180631, 11, 111, 255],
[-0.153193, -0.317818, 3, 167, 255],
[0.121182, 0.153193, 3, 167, 255],
[0.171484, 0.326963, 11, 111, 255],
[-0.285807, 0.006860, 56, 65, 255],
[-0.304098, 0.061735, 56, 65, 255],
[-0.093745, 0.285807, 56, 65, 255],
[0.052588, -0.235506, 0, 215, 232],
[-0.226359, -0.212640, 3, 167, 255],
[-0.180631, 0.089172, 11, 111, 255],
[-0.020578, -0.299526, 0, 215, 232],
[0.070881, 0.180630, 11, 111, 255],
[0.048016, 0.212641, 56, 65, 255],
[-0.006860, 0.258369, 56, 65, 255],
[-0.107464, 0.102891, 56, 65, 255],
[-0.029724, 0.345255, 56, 65, 255],
[-0.377266, -0.034296, 56, 65, 255],
[-0.322391, 0.025150, 56, 65, 255],
[-0.244651, 0.185204, 76, 25, 214],
[-0.125756, -0.262943, 3, 167, 255],
[-0.125756, 0.230932, 56, 65, 255],
[0.235505, 0.006860, 3, 167, 255],
[0.166911, 0.221786, 11, 111, 255],
[-0.217214, 0.221786, 76, 25, 214],
[-0.057162, -0.317818, 0, 215, 232],
[0.093744, -0.249225, 0, 215, 232],
[0.116609, 0.011432, 3, 167, 255],
[0.144046, 0.011432, 3, 167, 255],
[0.002286, 0.052588, 3, 167, 255],
[0.057161, 0.057161, 11, 111, 255],
[-0.372693, 0.112036, 76, 25, 214],
[-0.185203, 0.112036, 56, 65, 255],
[-0.020579, 0.112036, 11, 111, 255],
[-0.089171, 0.125755, 11, 111, 255],
[-0.272089, 0.153193, 56, 65, 255],
[0.258369, 0.244650, 3, 167, 255],
[0.016005, 0.290380, 56, 65, 255],
[0.180630, 0.299525, 11, 111, 255],
[0.006859, 0.368119, 56, 65, 255],
[-0.349828, -0.006859, 56, 65, 255],
[0.281234, 0.253797, 3, 167, 255],
[-0.125755, 0.139474, 56, 65, 255],
[-0.084600, 0.162338, 56, 65, 255],
[-0.189775, 0.253797, 56, 65, 255],
[-0.340682, -0.057162, 56, 65, 255],
[-0.340682, -0.025151, 56, 65, 255],
[-0.308671, 0.244650, 56, 65, 255],
[-0.272088, 0.258369, 76, 25, 214],
[0.322390, 0.208068, 3, 167, 255],
[-0.240079, 0.130329, 56, 65, 255],
[-0.313245, 0.153193, 56, 65, 255],
[0.217213, 0.043443, 3, 167, 255],
[-0.262943, -0.194350, 3, 167, 255],
[0.166911, -0.235506, 0, 215, 232],
[0.166912, 0.002286, 3, 167, 255],
[0.112036, 0.194349, 11, 111, 255],
[0.034297, -0.336109, 0, 215, 232],
[-0.089172, -0.299526, 3, 167, 255],
[-0.061734, -0.281234, 3, 167, 255],
[-0.240078, 0.276661, 56, 65, 255],
[0.002286, -0.272089, 3, 167, 255],
[0.061735, -0.212640, 3, 167, 255],
[0.089172, -0.011432, 3, 167, 255],
[0.208068, 0.002286, 3, 167, 255],
[0.304099, 0.034297, 0, 215, 232],
[0.048016, 0.089172, 3, 167, 255],
[0.098318, 0.102891, 3, 167, 255],
[0.290380, 0.217213, 3, 167, 255],
[-0.098318, -0.267515, 0, 215, 232],
[-0.016005, 0.285807, 56, 65, 255],
[0.125755, 0.345255, 56, 65, 255],
[0.194349, -0.011432, 0, 215, 232],
[-0.139475, -0.336109, 3, 167, 255],
[0.043443, 0.180630, 11, 111, 255],
[-0.176057, 0.322391, 56, 65, 255],
[-0.240078, -0.290381, 3, 167, 255],
[-0.189777, -0.267515, 3, 167, 255],
[-0.404703, -0.061734, 56, 65, 255],
[0.180630, 0.025150, 3, 167, 255],
[0.290380, -0.002287, 3, 167, 255],
[-0.011432, 0.080025, 11, 111, 255],
[-0.400131, 0.084599, 76, 25, 214],
[0.121183, -0.020578, 3, 167, 255],
[0.107464, -0.006859, 3, 167, 255],
[0.130329, 0.084599, 11, 111, 255],
[-0.029725, 0.139474, 11, 111, 255],
[-0.048016, 0.276661, 56, 65, 255],
[-0.340682, 0.029724, 56, 65, 255],
[-0.272089, 0.080025, 56, 65, 255],
[0.121182, -0.189776, 3, 167, 255],
[0.038870, 0.358974, 56, 65, 255],
[0.354400, 0.134900, 3, 167, 255],
[0.377266, -0.057162, 41, 247, 187],
[0.340682, -0.052589, 0, 215, 232],
[0.326963, -0.016006, 0, 215, 232],
[0.134902, 0.107463, 3, 167, 255],
[-0.038870, 0.235505, 56, 65, 255],
[-0.281235, -0.226359, 11, 111, 255],
[-0.185203, 0.153193, 56, 65, 255],
[0.084599, -0.180631, 3, 167, 255],
[0.130328, 0.048016, 3, 167, 255],
[0.016005, 0.203494, 11, 111, 255],
[-0.267516, -0.157765, 11, 111, 255],
[0.025151, 0.162338, 11, 111, 255],
[0.272088, 0.016005, 3, 167, 255],
[-0.089171, -0.363546, 3, 167, 255],
[-0.043443, -0.262943, 3, 167, 255],
[-0.112037, 0.358974, 56, 65, 255],
[-0.125756, 0.107463, 11, 111, 255],
[-0.372693, -0.116609, 56, 65, 255],
[-0.075453, 0.354400, 56, 65, 255],
[0.144047, 0.034297, 3, 167, 255],
[0.011432, 0.226360, 11, 111, 255],
[0.025150, 0.240079, 11, 111, 255],
[0.025150, -0.212640, 3, 167, 255],
[-0.130328, 0.253797, 56, 65, 255],
[-0.290381, -0.249225, 3, 167, 255],
[-0.217214, 0.098318, 56, 65, 255],
[-0.244651, -0.217214, 11, 111, 255],
[-0.249225, -0.180631, 11, 111, 255],
[0.208067, -0.034296, 3, 167, 255],
[-0.107464, 0.317818, 56, 65, 255],
[-0.221787, 0.240079, 56, 65, 255],
[0.025152, -0.258370, 0, 215, 232],
[-0.349828, -0.107464, 11, 111, 255],
[0.153193, -0.029725, 3, 167, 255],
[-0.139474, 0.171485, 56, 65, 255],
[-0.326964, 0.180630, 56, 65, 255],
[-0.409276, 0.016005, 56, 65, 255],
[0.253797, 0.043443, 3, 167, 255],
[0.098318, 0.162338, 11, 111, 255],
[-0.048015, 0.166911, 11, 111, 255],
[-0.285807, 0.185204, 56, 65, 255],
[-0.011432, -0.249225, 3, 167, 255],
[-0.180630, -0.299526, 3, 167, 255],
[0.203494, 0.020579, 3, 167, 255],
[-0.052589, -0.368120, 0, 215, 232],
[-0.112037, -0.317818, 3, 167, 255],
[-0.144047, -0.299526, 3, 167, 255],
[-0.153192, -0.281234, 3, 167, 255],
[-0.162339, -0.249225, 3, 167, 255],
[-0.075453, 0.038869, 11, 111, 255],
[-0.290380, 0.098318, 56, 65, 255],
[-0.002286, 0.116610, 11, 111, 255],
[-0.057162, 0.294954, 56, 65, 255],
[0.413849, 0.002286, 0, 215, 232],
[-0.125755, 0.336110, 56, 65, 255],
[-0.336109, -0.139475, 11, 111, 255],
[-0.176057, 0.285807, 56, 65, 255],
[-0.075453, -0.317818, 0, 215, 232],
[-0.157766, 0.230932, 56, 65, 255],
[-0.336110, 0.144047, 56, 65, 255],
[-0.107463, 0.080025, 11, 111, 255],
[-0.070881, 0.089172, 11, 111, 255],
[-0.130328, 0.203494, 56, 65, 255],
[-0.075453, 0.299525, 56, 65, 255],
[-0.331536, 0.212641, 76, 25, 214],
[-0.253797, 0.066307, 56, 65, 255],
[0.006860, 0.089172, 11, 111, 255],
[0.084600, -0.267515, 0, 215, 232],
[-0.276662, -0.262943, 3, 167, 255],
[-0.304100, 0.134900, 76, 25, 214],
[-0.317817, 0.002286, 56, 65, 255],
[0.093744, 0.029724, 11, 111, 255],
[0.226359, 0.061735, 3, 167, 255],
[0.194348, 0.075454, 3, 167, 255],
[-0.194350, 0.080025, 56, 65, 255],
[0.107463, 0.084599, 3, 167, 255],
[-0.016005, 0.171485, 11, 111, 255],
[-0.304100, 0.212641, 56, 65, 255],
[-0.006859, -0.336109, 3, 167, 255],
[0.080025, 0.002286, 3, 167, 255],
[0.253797, 0.002286, 0, 215, 232],
[0.052588, -0.006859, 3, 167, 255],
[-0.034296, 0.075454, 11, 111, 255],
[-0.043443, 0.203494, 56, 65, 255],
[-0.294953, -0.166912, 11, 111, 255],
[-0.267516, 0.038869, 56, 65, 255],
[-0.358975, 0.057161, 56, 65, 255],
[0.299527, -0.048015, 0, 215, 232],
[0.020578, 0.057161, 11, 111, 255],
[0.144047, -0.212640, 0, 215, 232],
[0.285807, 0.038869, 3, 167, 255],
[0.185203, 0.102891, 3, 167, 255],
[-0.203494, 0.144047, 56, 65, 255],
[0.002287, 0.322391, 56, 65, 255],
[-0.235506, 0.148619, 76, 25, 214],
[-0.107464, 0.194349, 56, 65, 255],
[-0.093745, 0.240079, 56, 65, 255],
[0.235505, 0.029724, 3, 167, 255],
[0.093744, -0.198921, 3, 167, 255],
[-0.029723, -0.313245, 0, 215, 232],
[-0.080026, -0.285807, 0, 215, 232],
[-0.148620, 0.139474, 56, 65, 255],
[-0.221786, 0.157766, 56, 65, 255],
[-0.212641, 0.253797, 56, 65, 255],
[0.048016, -0.349828, 0, 215, 232],
[0.377266, -0.002287, 0, 215, 232],
[0.381838, 0.116610, 3, 167, 255],
[-0.057162, 0.130329, 11, 111, 255],
[-0.070881, 0.189775, 11, 111, 255],
[-0.070881, 0.217213, 11, 111, 255],
[0.034296, 0.125755, 11, 111, 255],
[0.006860, 0.134900, 11, 111, 255],
[0.153193, -0.226359, 0, 215, 232],
[0.057161, -0.262943, 3, 167, 255],
[0.290380, -0.020578, 0, 215, 232],
[0.043443, 0.011432, 3, 167, 255],
[-0.372693, 0.025150, 56, 65, 255],
[0.006860, 0.025150, 11, 111, 255],
[0.066307, 0.043443, 3, 167, 255],
[-0.253796, 0.048016, 56, 65, 255],
[-0.322390, 0.057161, 56, 65, 255],
[0.139474, 0.066307, 11, 111, 255],
[0.240079, 0.070880, 0, 215, 232],
[-0.345256, 0.107463, 56, 65, 255],
[-0.226359, 0.189775, 56, 65, 255],
[-0.272089, 0.194349, 76, 25, 214],
[-0.098318, 0.208068, 11, 111, 255],
[-0.020578, 0.208068, 56, 65, 255],
[-0.258370, 0.221786, 56, 65, 255],
[-0.185203, 0.235505, 56, 65, 255],
[0.016005, 0.258369, 11, 111, 255],
[-0.080026, -0.349828, 0, 215, 232],
[-0.171484, -0.285807, 3, 167, 255],
[0.034297, -0.240078, 3, 167, 255],
[-0.322391, -0.235506, 11, 111, 255],
[-0.299527, -0.144046, 11, 111, 255],
[-0.340682, -0.075453, 56, 65, 255],
[-0.313245, -0.052589, 11, 111, 255],
[-0.400130, -0.043443, 56, 65, 255],
[-0.317818, -0.025151, 56, 65, 255],
[-0.354402, 0.020579, 56, 65, 255],
[-0.043442, 0.029724, 11, 111, 255],
[-0.400131, 0.048016, 76, 25, 214],
[-0.285808, 0.066307, 56, 65, 255],
[-0.134901, 0.075454, 56, 65, 255],
[-0.066307, 0.153193, 56, 65, 255],
[-0.285807, 0.244650, 76, 25, 214],
[-0.006859, 0.016005, 3, 167, 255],
[0.080025, 0.194349, 11, 111, 255],
[0.093744, 0.221786, 56, 65, 255],
[-0.368119, -0.052589, 56, 65, 255],
[-0.144047, -0.267515, 3, 167, 255],
[-0.052589, 0.098318, 11, 111, 255],
[-0.107464, 0.226360, 56, 65, 255],
[0.130328, 0.166911, 11, 111, 255],
[-0.016006, 0.153193, 11, 111, 255],
[-0.093745, 0.345255, 56, 65, 255],
[-0.093745, 0.326963, 56, 65, 255],
[-0.116609, -0.276662, 3, 167, 255],
[0.381839, -0.038870, 0, 215, 232],
[-0.020578, -0.363546, 0, 215, 232],
[-0.171484, -0.313245, 3, 167, 255],
[0.025152, 0.075454, 11, 111, 255],
[-0.208068, -0.290381, 11, 111, 255],
[-0.070881, 0.235505, 11, 111, 255],
[0.038870, 0.235505, 11, 111, 255],
[-0.139473, 0.281235, 56, 65, 255],
[-0.294953, -0.212640, 11, 111, 255],
[0.409277, -0.034296, 0, 215, 232],
[0.212641, 0.089172, 3, 167, 255],
[0.139474, -0.016006, 3, 167, 255],
[-0.372692, -0.006859, 56, 65, 255],
[0.107464, 0.025150, 3, 167, 255],
[-0.075453, 0.116610, 11, 111, 255],
[0.070880, 0.240079, 11, 111, 255],
[0.002286, 0.294954, 56, 65, 255],
[-0.400131, -0.016006, 76, 25, 214],
[-0.294953, -0.102890, 11, 111, 255],
[-0.336109, 0.066307, 76, 25, 214],
[0.002287, -0.226359, 3, 167, 255],
[-0.066307, 0.262943, 56, 65, 255],
[-0.093745, 0.089172, 11, 111, 255],
[-0.368120, -0.157765, 11, 111, 255],
[-0.089171, -0.331537, 0, 215, 232],
[0.331536, 0.016005, 0, 215, 232],
[0.386411, 0.016005, 0, 215, 232],
[0.304098, 0.208068, 3, 167, 255],
[-0.313245, 0.121182, 56, 65, 255],
[-0.034297, -0.349828, 0, 215, 232],
[0.208068, 0.308672, 11, 111, 255],
[0.308672, -0.061734, 0, 215, 232],
[-0.226359, 0.066307, 56, 65, 255],
[0.016005, -0.358975, 0, 215, 232],
[-0.066307, -0.354401, 0, 215, 232],
[-0.217213, -0.313245, 3, 167, 255],
[-0.107464, -0.304100, 0, 215, 232],
[-0.272089, -0.290381, 11, 111, 255],
[-0.253796, -0.285807, 3, 167, 255],
[-0.025151, -0.285807, 0, 215, 232],
[-0.130328, -0.244651, 3, 167, 255],
[0.139473, -0.244651, 0, 215, 232],
[-0.258370, -0.235506, 3, 167, 255],
[0.066307, -0.230932, 0, 215, 232],
[0.121182, -0.230932, 0, 215, 232],
[0.084599, -0.212640, 0, 215, 232],
[-0.354402, -0.185203, 11, 111, 255],
[-0.322391, -0.166912, 11, 111, 255],
[-0.313245, -0.080026, 11, 111, 255],
[0.326963, -0.038870, 0, 215, 232],
[-0.299526, 0.029724, 56, 65, 255],
[-0.066308, 0.326963, 56, 65, 255],
[-0.052589, -0.331537, 0, 215, 232],
[0.048016, -0.203495, 0, 215, 232],
[-0.395557, 0.002286, 56, 65, 255],
[0.020579, 0.002286, 11, 111, 255],
[0.189775, 0.052588, 3, 167, 255],
[0.166911, 0.057161, 11, 111, 255],
[0.084599, 0.070880, 3, 167, 255],
[0.084599, 0.098318, 3, 167, 255],
[0.029724, 0.107463, 3, 167, 255],
[0.358974, 0.121182, 3, 167, 255],
[0.144047, 0.125755, 11, 111, 255],
[0.089172, 0.134900, 11, 111, 255],
[0.139474, 0.144047, 3, 167, 255],
[0.061734, 0.166911, 11, 111, 255],
[-0.358975, 0.185204, 76, 25, 214],
[-0.304100, 0.194349, 56, 65, 255],
[0.258369, 0.203494, 11, 111, 255],
[0.134900, 0.240079, 11, 111, 255],
[0.052588, 0.249224, 11, 111, 255],
[-0.162339, 0.258369, 76, 25, 214],
[-0.244651, 0.262943, 56, 65, 255],
[0.057161, 0.345255, 11, 111, 255],
[0.084599, 0.345255, 11, 111, 255],
[-0.322390, 0.166911, 56, 65, 255],
[-0.025151, 0.299525, 11, 111, 255],
[0.230932, -0.006859, 3, 167, 255],
[-0.025150, -0.331537, 0, 215, 232],
[0.112036, -0.249225, 0, 215, 232],
[0.267516, 0.034297, 0, 215, 232],
[-0.098318, 0.061735, 11, 111, 255],
[-0.208068, 0.130329, 56, 65, 255],
[-0.084600, 0.180630, 56, 65, 255],
[-0.258370, -0.208068, 11, 111, 255],
[-0.313245, -0.098318, 11, 111, 255],
[-0.267516, 0.093744, 56, 65, 255],
[-0.267516, 0.121182, 56, 65, 255],
[-0.390984, -0.066307, 11, 111, 255],
[-0.354401, 0.084599, 76, 25, 214],
[-0.249224, 0.098318, 56, 65, 255],
[-0.226359, 0.125755, 56, 65, 255],
[0.002286, 0.148619, 56, 65, 255],
[-0.171484, 0.162338, 56, 65, 255],
[-0.185203, 0.208068, 56, 65, 255],
[-0.244650, 0.217213, 56, 65, 255],
[-0.048016, 0.326963, 56, 65, 255],
[0.116609, 0.052588, 3, 167, 255],
[0.153193, 0.322391, 11, 111, 255],
[0.107463, 0.336110, 56, 65, 255],
[0.390984, -0.011432, 0, 215, 232],
[0.267515, -0.038870, 0, 215, 232],
[0.404703, 0.061735, 0, 215, 232],
[0.011432, 0.102891, 11, 111, 255],
[0.358973, -0.025151, 0, 215, 232],
[0.176057, 0.080025, 3, 167, 255],
[0.322391, 0.185204, 3, 167, 255],
[-0.052589, 0.217213, 11, 111, 255],
[-0.189776, 0.276661, 76, 25, 214],
[-0.125756, 0.304099, 76, 25, 214],
[-0.345256, -0.212640, 11, 111, 255],
[-0.016005, 0.048016, 11, 111, 255],
[-0.034296, 0.176057, 56, 65, 255],
[-0.011432, 0.272088, 56, 65, 255],
[-0.057161, 0.354400, 56, 65, 255],
[-0.208068, 0.281235, 56, 65, 255],
[0.322391, -0.002287, 0, 215, 232],
[0.354402, 0.006860, 0, 215, 232],
[0.034297, 0.034297, 11, 111, 255],
[-0.006859, 0.066307, 11, 111, 255],
[0.121182, 0.112036, 11, 111, 255],
[-0.153193, 0.116610, 11, 111, 255],
[0.116610, 0.134900, 11, 111, 255],
[0.276661, 0.185204, 11, 111, 255],
[-0.285807, 0.217213, 76, 25, 214],
[0.235505, 0.226360, 11, 111, 255],
[-0.048016, 0.308672, 56, 65, 255],
[0.176058, 0.313244, 11, 111, 255],
[-0.180631, 0.336110, 56, 65, 255],
[-0.313244, -0.221787, 11, 111, 255],
[-0.372693, 0.048016, 76, 25, 214],
[-0.107464, -0.363546, 3, 167, 255],
[-0.006859, -0.304100, 0, 215, 232],
[-0.226359, -0.285807, 3, 167, 255],
[-0.057162, -0.267515, 3, 167, 255],
[-0.070881, -0.253796, 3, 167, 255],
[-0.180631, -0.221787, 3, 167, 255],
[-0.212640, -0.217214, 3, 167, 255],
[-0.313245, -0.194350, 11, 111, 255],
[-0.276662, -0.189776, 11, 111, 255],
[0.070880, -0.185203, 3, 167, 255],
[-0.322390, -0.148620, 11, 111, 255],
[-0.368120, -0.130328, 56, 65, 255],
[-0.304100, -0.125756, 11, 111, 255],
[-0.276662, -0.121182, 11, 111, 255],
[-0.363546, -0.084600, 11, 111, 255],
[-0.390984, -0.029725, 56, 65, 255],
[0.244650, -0.029725, 3, 167, 255],
[-0.075453, 0.070880, 11, 111, 255],
[-0.208068, 0.084599, 56, 65, 255],
[-0.139475, 0.102891, 11, 111, 255],
[-0.358975, 0.116610, 76, 25, 214],
[-0.025150, 0.125755, 11, 111, 255],
[-0.349828, 0.166911, 56, 65, 255],
[-0.235506, 0.244650, 56, 65, 255],
[-0.098317, 0.253797, 56, 65, 255],
[-0.226359, 0.272088, 56, 65, 255],
[0.020578, -0.340682, 0, 215, 232],
[0.130328, 0.016005, 3, 167, 255],
[0.395558, 0.043443, 0, 215, 232],
[0.011433, 0.166911, 11, 111, 255],
[-0.112037, 0.144047, 56, 65, 255],
[-0.194349, 0.180630, 56, 65, 255],
[-0.349828, -0.162339, 11, 111, 255],
[-0.322390, 0.093744, 56, 65, 255],
[-0.285807, 0.112036, 56, 65, 255],
[-0.258370, 0.189775, 56, 65, 255],
[-0.157765, -0.349828, 3, 167, 255],
[0.002287, -0.322390, 0, 215, 232],
[-0.235505, -0.304100, 3, 167, 255],
[0.093745, -0.230932, 0, 215, 232],
[0.180631, -0.006859, 3, 167, 255],
[-0.299525, 0.162338, 56, 65, 255],
[-0.144046, 0.313244, 76, 25, 214],
[0.134900, 0.331536, 11, 111, 255],
[0.157766, 0.038869, 3, 167, 255],
[-0.038870, 0.253797, 56, 65, 255],
[-0.153193, -0.235506, 11, 111, 255],
[-0.157765, 0.066307, 11, 111, 255],
[-0.116609, 0.258369, 56, 65, 255],
[-0.153192, 0.157766, 56, 65, 255],
[-0.390985, 0.070880, 56, 65, 255],
[-0.349828, 0.139474, 56, 65, 255],
[-0.331536, 0.194349, 76, 25, 214],
[-0.198922, 0.066307, 56, 65, 255],
[-0.134902, 0.217213, 56, 65, 255],
[0.244652, -0.057162, 0, 215, 232],
[0.048016, 0.125755, 3, 167, 255],
[-0.198922, 0.116610, 56, 65, 255],
[-0.020578, 0.317818, 56, 65, 255],
[-0.057161, 0.057161, 11, 111, 255],
[0.340683, -0.016006, 0, 215, 232],
[0.212640, 0.075454, 3, 167, 255],
[0.020579, 0.130329, 11, 111, 255],
[-0.075454, 0.313244, 56, 65, 255],
[-0.006859, -0.349828, 0, 215, 232],
[0.075453, 0.016005, 3, 167, 255],
[-0.034297, 0.107463, 11, 111, 255],
[-0.052589, 0.244650, 11, 111, 255],
[-0.162339, 0.272088, 56, 65, 255],
[0.002286, 0.308672, 11, 111, 255],
[-0.034296, 0.331536, 56, 65, 255],
[-0.016005, 0.354400, 56, 65, 255],
[-0.299525, 0.002286, 56, 65, 255],
[-0.102890, -0.331537, 3, 167, 255],
[-0.281234, -0.276662, 3, 167, 255],
[-0.253796, -0.272089, 11, 111, 255],
[-0.029725, -0.267515, 3, 167, 255],
[-0.112037, -0.262943, 3, 167, 255],
[0.070880, -0.262943, 0, 215, 232],
[-0.240078, -0.258370, 11, 111, 255],
[-0.043443, -0.249225, 0, 215, 232],
[0.020579, -0.244651, 3, 167, 255],
[-0.180631, -0.235506, 11, 111, 255],
[0.130329, -0.212640, 0, 215, 232],
[-0.294953, -0.198921, 11, 111, 255],
[0.061735, -0.198921, 0, 215, 232],
[-0.326964, -0.080026, 56, 65, 255],
[-0.299526, -0.070881, 56, 65, 255],
[-0.372693, -0.066307, 11, 111, 255],
[-0.326964, -0.052589, 56, 65, 255],
[0.281235, -0.048015, 0, 215, 232],
[0.294954, -0.034296, 3, 167, 255],
[0.189775, -0.025151, 0, 215, 232],
[0.230932, -0.020578, 0, 215, 232],
[0.276661, -0.020578, 0, 215, 232],
[-0.313245, -0.011432, 56, 65, 255],
[0.258369, -0.011432, 0, 215, 232],
[-0.349828, 0.006860, 56, 65, 255],
[0.221786, 0.006860, 0, 215, 232],
[0.400130, 0.016005, 0, 215, 232],
[-0.020578, 0.034297, 11, 111, 255],
[0.052588, 0.043443, 11, 111, 255],
[-0.130328, 0.061735, 11, 111, 255],
[-0.363547, 0.070880, 56, 65, 255],
[-0.240078, 0.070880, 56, 65, 255],
[-0.308671, 0.075454, 76, 25, 214],
[-0.121182, 0.075454, 11, 111, 255],
[-0.377265, 0.098318, 76, 25, 214],
[-0.020579, 0.098318, 11, 111, 255],
[-0.326964, 0.121182, 76, 25, 214],
[-0.125756, 0.121182, 56, 65, 255],
[0.089172, 0.148619, 11, 111, 255],
[0.057162, 0.180630, 11, 111, 255],
[-0.084600, 0.208068, 56, 65, 255],
[0.070880, 0.226360, 11, 111, 255],
[-0.025151, 0.230932, 56, 65, 255],
[-0.258370, 0.235505, 76, 25, 214],
[-0.157766, 0.244650, 56, 65, 255],
[-0.011432, 0.244650, 56, 65, 255],
[-0.272089, 0.272088, 56, 65, 255],
[-0.144046, 0.349829, 76, 25, 214],
[0.020578, 0.368119, 56, 65, 255],
[-0.340682, -0.038870, 56, 65, 255],
[-0.331537, 0.226360, 76, 25, 214],
[-0.317818, 0.212641, 76, 25, 214],
[0.057162, -0.249225, 0, 215, 232],
[-0.066307, 0.340682, 56, 65, 255],
[-0.171484, 0.230932, 76, 25, 214],
[-0.194348, -0.240078, 3, 167, 255],
[-0.281234, -0.139475, 11, 111, 255],
[-0.294953, -0.116609, 11, 111, 255],
[0.107463, -0.198921, 0, 215, 232],
[-0.043442, 0.349829, 56, 65, 255],
[-0.386412, 0.052588, 56, 65, 255],
[0.377266, 0.093744, 3, 167, 255],
[0.240079, 0.148619, 11, 111, 255],
[0.267516, 0.253797, 3, 167, 255],
[0.253797, 0.267516, 3, 167, 255],
];
+19 -5
View File
@@ -3,7 +3,8 @@ import { compilePetTelemetry, decodePetJSONL, PetLiveTape } from './pet-telemetr
import { ARCH_OF, digest, layout, PetSim } from './pet-sim.js';
import { renderPetPCM, type PetVoice } from './pet-audio.js';
import { PetEngineTelemetry, type EngineOwnerProjection } from './pet-engine.js';
import { blendDotFields, dotFieldKey, projectDotField, type DotField } from './dot-field.js';
import { blendDotFields, dotFieldKey, projectDotField, textLanguage, type DotField, type DotOptions } from './dot-field.js';
import { DOT_LABELS, type GlyphLanguage } from './dot-glyphs.js';
/** Synchronous native boundary: JSON state and directly transferable PCM.
* Native hosts share the actual world / score implementation, not a rewrite. */
@@ -12,6 +13,7 @@ export class PetNative {
private engine = new PetEngineTelemetry();
private expressionActivity = this.engine.activity(0);
private expressionTransition?: { at: number; from: DotField };
private expressionOptions: Required<DotOptions> = { bodyStyle: 'smooth', textLanguage: 'en' };
private engineTick = 0;
private segment?: PetSegment;
private liveTape = new PetLiveTape();
@@ -26,7 +28,7 @@ export class PetNative {
snapshot(): string { return JSON.stringify({ ...this.world.frame, voices: this.world.voices, digest: digest(this.world.sim) }); }
private expression(): DotField {
const { frame } = this.world;
const target = projectDotField(this.expressionActivity, frame.timeMs, false, this.expressionTransition?.at ?? 0);
const target = projectDotField(this.expressionActivity, frame.timeMs, false, this.expressionTransition?.at ?? 0, this.expressionOptions);
return this.expressionTransition
? blendDotFields(this.expressionTransition.from, target, (frame.timeMs - this.expressionTransition.at) / 560)
: target;
@@ -38,7 +40,15 @@ export class PetNative {
}
/** View-only projection. Display cadence and accessibility preferences never
* advance the owner, consume randomness, or change its score/checkpoint. */
presentation(): string {
presentation(options: DotOptions = {}, interfaceLanguage = 'en'): string {
if (!['smooth', 'pixel'].includes(options.bodyStyle ?? 'smooth')
|| !['auto', 'en', 'zh-Hans', 'zh-Hant', 'ja'].includes(options.textLanguage ?? 'auto'))
throw new Error('Invalid pet presentation preferences.');
const next = { bodyStyle: options.bodyStyle ?? 'smooth', textLanguage: textLanguage(options.textLanguage, interfaceLanguage) };
if (next.bodyStyle !== this.expressionOptions.bodyStyle || next.textLanguage !== this.expressionOptions.textLanguage) {
const from = this.expression(); this.expressionOptions = next;
this.expressionTransition = { at: this.world.frame.timeMs, from };
}
const { sim, frame } = this.world;
const activity = this.engine.activity(frame.timeMs);
const observed = activity.observed && activity.freshness === 'fresh';
@@ -52,9 +62,13 @@ export class PetNative {
podSlots: peers.length >= 3 ? peers.map(p => [[0, 2, 4, 1, 3, 5][p.slot], p.phase] as const) : undefined });
this.stillProjection = { key, style: still.frame };
}
const still = projectDotField(activity, frame.timeMs, true);
const still = projectDotField(activity, frame.timeMs, true, 0, this.expressionOptions);
const language = this.expressionOptions.textLanguage as GlyphLanguage;
const label = activity.authoritativePresence === 'needs_you' ? 'waiting'
: activity.authoritativePresence === 'done' ? 'done' : activity.activityKind;
return JSON.stringify({ ...frame, digest: digest(sim), state, style: { ...sim.frame, hollow: !observed }, activity,
...this.expression(),
...this.expression(), displayLanguage: language,
displayPhrase: observed && label ? DOT_LABELS[language][label] : '',
still: { ...still, style: { ...this.stillProjection.style, hollow: !observed }, state } });
}
/** Losing a producer invalidates outstanding coverage, never the creature. */