diff --git a/categories/krea2_pov_pose_variants.json b/categories/krea2_pov_pose_variants.json index 20ad0e5..d84a1d0 100644 --- a/categories/krea2_pov_pose_variants.json +++ b/categories/krea2_pov_pose_variants.json @@ -391,14 +391,14 @@ "atlas_folders": ["blowjob_top_view"], "action_family": "oral", "position_keys": ["kneeling", "top_down_oral"], - "canonical_geometry": "Nadir-angle standing male POV top-view oral view: the viewer looks almost straight down from his torso toward the floor, nearby floor plane dominates the image, the viewer abdomen, shorts, thighs, and feet frame the lower foreground, the shaft is a short centered vertical column, and one woman kneels directly below between his feet with hair crown, forehead, shoulders, hands, knees, mouth, and shaft alignment visible from above.", + "canonical_geometry": "Nadir-angle standing male POV top-view oral view: the viewer looks almost straight down from his torso toward the floor, nearby floor plane dominates the image, the viewer abdomen, shorts, thighs, and feet frame the lower foreground, the shaft is a short centered vertical column, and the woman kneels directly below between his feet with hair crown, forehead, shoulders, hands, knees, mouth, and shaft alignment visible from above.", "prompt_cues": [ "nadir-angle standing male POV top-view oral position", "viewer looks almost straight down from his torso toward the floor", "nearby carpet/floor plane dominates the image", "viewer abdomen, shorts, thighs, and feet frame the lower foreground", "shaft is a short centered vertical column", - "one woman kneels directly below the viewer between his feet", + "the woman kneels directly below the viewer between his feet", "her mouth seals around the centered shaft", "one hand wraps the base", "hair crown, forehead, shoulders, hands, and knees are visible from above", @@ -424,7 +424,7 @@ "evidence": { "fixed_seed_tests": ["4242424242"], "guide_section": "docs/krea2-prompt-guide.md#blowjob-top-view--overhead-vertical-shaft", - "notes": "Same-sampler source 46/47 A/B showed that top-down oral hierarchy tightens hand-at-base support and centered shaft-to-mouth alignment over generic kneeling oral. A follow-up axis loop on the same seed showed that generic steep-overhead wording can still feel horizontal, while nadir-angle standing male POV plus a dominating nearby floor plane, one woman directly between the viewer's feet, top-down office anchors, and a short centered vertical shaft column gives the strongest atlas-like verticality. Avoid plumb-line/map wording because Krea2 can literalize it as drawn graphics. Keep candidate until another source or seed repeats the nadir-angle axis." + "notes": "Same-sampler source 46/47 A/B showed that top-down oral hierarchy tightens hand-at-base support and centered shaft-to-mouth alignment over generic kneeling oral. A follow-up axis loop on the same seed showed that generic steep-overhead wording can still feel horizontal, while nadir-angle standing male POV plus a dominating nearby floor plane, the woman directly between the viewer's feet, top-down office anchors, and a short centered vertical shaft column gives the strongest atlas-like verticality. Avoid plumb-line/map wording because Krea2 can literalize it as drawn graphics. Keep candidate until another source or seed repeats the nadir-angle axis." } }, { diff --git a/docs/krea2-ab-methodology.md b/docs/krea2-ab-methodology.md index 8ddfaa6..4cd01c6 100644 --- a/docs/krea2-ab-methodology.md +++ b/docs/krea2-ab-methodology.md @@ -326,7 +326,7 @@ scene. The corrected test pattern keeps the coworking location intact: editing the generator, wait for `sxcp_eval_in` to advance to the new turn, and compare each image against the atlas verticality criteria. The useful axis is `nadir-angle` or `bird's-eye` plus standing male POV, nearby floor plane - dominating the image, one woman directly below between the viewer's feet, and + dominating the image, the woman directly below between the viewer's feet, and top-down office anchors. Avoid `plumb-line` and `map` in generator prompts because Krea2 can literalize them as drawn graphics. - `2026-06-29`: For quick wording-axis search, prefer a batched prompt-probe diff --git a/docs/krea2-eval-log.json b/docs/krea2-eval-log.json index d1a3455..ce241d6 100644 --- a/docs/krea2-eval-log.json +++ b/docs/krea2-eval-log.json @@ -1449,7 +1449,7 @@ "result": "accepted", "decision": "provisional_generator_patch", "baseline_prompt_summary": "Earlier top-view probes on the same seed used vertical-shaft or generic steep-overhead wording and could still read too frontal, too horizontal, or too dependent on long office depth cues.", - "candidate_prompt_summary": "Prompt-only axis loop tested near-vertical floor-plane, plumb-line/map, nadir-angle, and bird's-eye standing male POV wording. The accepted axis is nadir-angle or bird's-eye standing male POV, viewer looking almost straight down from torso to floor, nearby carpet/floor plane dominating, one woman kneeling directly below between the viewer's feet, top-down office anchors, and a short centered vertical shaft column.", + "candidate_prompt_summary": "Prompt-only axis loop tested near-vertical floor-plane, plumb-line/map, nadir-angle, and bird's-eye standing male POV wording. The accepted axis is nadir-angle or bird's-eye standing male POV, viewer looking almost straight down from torso to floor, nearby carpet/floor plane dominating, the woman kneeling directly below between the viewer's feet, top-down office anchors, and a short centered vertical shaft column.", "observation": "Turns 67, 69, and 70 on sampler seed 4242424242 produced the first clearly atlas-like verticality after the user rejected horizontal probes: viewer abdomen/shorts/feet anchor the lower edge, the woman is directly below between the viewer's feet, floor plane and nearby office furniture read as top-down anchors, her hair crown/forehead/shoulders/hands/knees are visible from above, and mouth contact stays centered. Turn 68 showed that plumb-line and map are unsafe generator words because Krea2 literalized them into drawn graphics. Mirror the nadir-angle/floor-plane wording as a provisional generator patch, but keep the catalog route candidate until another source or seed repeats it.", "baseline_image": "/media/unraid/comfyui/output/agent_bridge/img_e421319637bc45f0bb49bfc486c7f4ad.png", "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_ea922fa7bd6642f5bbe76b50f48b558b.png", @@ -1457,7 +1457,7 @@ { "source_case": "turn67 near-vertical floor-plane", "candidate_image": "/media/unraid/comfyui/output/agent_bridge/img_56d6738989794a569cfe45d79dd96e88.png", - "observation": "Strong vertical floor plane, one woman below the viewer, table and chair legs as top-down anchors, and coherent centered contact." + "observation": "Strong vertical floor plane, the woman below the viewer, table and chair legs as top-down anchors, and coherent centered contact." }, { "source_case": "turn68 plumb-line map", diff --git a/docs/krea2-prompt-guide.md b/docs/krea2-prompt-guide.md index 06f31f3..05c1882 100644 --- a/docs/krea2-prompt-guide.md +++ b/docs/krea2-prompt-guide.md @@ -795,7 +795,7 @@ Works better: - `nearby carpet/floor plane dominates the image` - `viewer abdomen, shorts, thighs, and feet frame the lower foreground` - `shaft appears as a short centered vertical column from the foreground` -- `one woman kneels directly below the viewer between his feet` +- `the woman kneels directly below the viewer between his feet` - `hair crown, forehead, shoulders, hands, and knees are visible from above` - `desk legs, chair wheels, carpet texture, and floor seams as top-down office anchors` - `mouth seals around the centered shaft` @@ -822,7 +822,7 @@ Correction probes: a later same-seed source-`46` probe showed that vertical center-shaft wording and generic steep-overhead wording can still feel too frontal or horizontal. A short axis loop found the stronger terms: `nadir-angle` or `bird's-eye` paired with `standing male POV`, `floor plane dominates`, nearby -top-down office anchors, `one woman directly below between his feet`, and +top-down office anchors, `the woman directly below between his feet`, and `short centered vertical column`. The `plumb-line` and `map` terms produced good geometry but literal drawn artifacts, so they should stay out of generator wording. diff --git a/krea_pov_actions.py b/krea_pov_actions.py index 1e2d992..d793f41 100644 --- a/krea_pov_actions.py +++ b/krea_pov_actions.py @@ -110,7 +110,7 @@ def _krea2_atlas_variant_sentence(axis_values: Any) -> str: if not variant: return "" cues = _unique_texts(list(variant.get("prompt_cues") or []) or [variant.get("canonical_geometry")]) - return _clean("; ".join(cues)).rstrip(".") + return _clean(". ".join(cues)).rstrip(".") def pov_ejaculation_target(context: str) -> str: @@ -575,7 +575,7 @@ def pov_hardcore_pose_sentence( return oral_sentence( "Nadir-angle standing male POV top-view oral position: the viewer looks almost straight down from his torso toward the floor, with nearby carpet/floor plane dominating the image; " "the viewer's abdomen, shorts, thighs, and feet frame the lower foreground, and the viewer's penis shaft appears as a short centered vertical column from the foreground; " - "one kneeling woman is directly below the viewer between his feet, her face tilts upward beneath the shaft, her mouth seals around it, and one hand wraps the base; " + "the kneeling woman is directly below the viewer between his feet, her face tilts upward beneath the shaft, her mouth seals around it, and one hand wraps the base; " "her hair crown, forehead, shoulders, hands, knees, and compact foreshortened torso are visible from above, with desk legs, chair wheels, carpet texture, and floor seams as top-down office anchors around her" ) if man_gives and not woman_gives: diff --git a/tools/prompt_smoke.py b/tools/prompt_smoke.py index 080ff53..33b1450 100644 --- a/tools/prompt_smoke.py +++ b/tools/prompt_smoke.py @@ -6843,6 +6843,11 @@ def smoke_krea2_pov_atlas_variant_prompt_routes() -> None: for cue in variant.get("prompt_cues") or []: cue_text = _expect_text(f"{key}.prompt_cue", cue, 8).lower() _expect(cue_text in prompt, f"{key} final Krea prompt lost atlas cue {cue_text!r}: {prompt}") + atlas_action_prompt = prompt.split(" camera is ", 1)[0] + _expect( + "; " not in atlas_action_prompt, + f"{key} final Krea prompt kept semicolon-delimited atlas cue formatting: {prompt}", + ) _expect( "framed as " not in prompt and "the image is framed as " not in prompt, f"{key} final Krea prompt kept generic composition text after atlas route: {prompt}", @@ -6871,10 +6876,12 @@ def smoke_krea2_pov_atlas_variant_prompt_routes() -> None: ): _expect(forbidden not in prompt, f"{key} final Krea prompt kept generic detail fragment {forbidden!r}: {prompt}") for required in ( + "the woman kneels directly below the viewer between his feet", "mouth seals around the centered shaft", "one hand wraps the base", ): _expect(required in prompt, f"{key} final Krea prompt lost compact contact cue {required!r}: {prompt}") + _expect("one woman" not in prompt, f"{key} final Krea prompt split the subject with 'one woman': {prompt}") for avoid in variant.get("avoid_cues") or []: avoid_text = _expect_text(f"{key}.avoid_cue", avoid, 4).lower() _expect(avoid_text not in prompt, f"{key} final Krea prompt leaked avoid cue {avoid_text!r}: {prompt}") @@ -10394,6 +10401,106 @@ def smoke_sxcp_mcp_client_cli_policy() -> None: _expect("--arguments-json" in call_help.stdout, "sxcp MCP client help lost JSON argument option") +def smoke_watch_prompt_image_folder_cli_policy() -> None: + helper_path = ROOT / "tools" / "watch_prompt_image_folder.sh" + _expect(helper_path.exists(), "watch prompt/image folder helper is missing") + _expect(helper_path.stat().st_mode & 0o111, "watch prompt/image folder helper must be executable") + + help_result = subprocess.run( + [str(helper_path), "--help"], + cwd=ROOT, + capture_output=True, + text=True, + check=False, + ) + _expect(help_result.returncode == 0, f"watch helper --help failed: {help_result.stderr}") + for term in ("--folder", "--target", "--notes", "--once", "--dry-run", "tmux send-keys"): + _expect(term in help_result.stdout, f"watch helper help lost {term!r}") + + with tempfile.TemporaryDirectory() as temp_dir: + folder = Path(temp_dir) + prompt_path = folder / "atlas_case_001.txt" + image_path = folder / "atlas_case_001.png" + state_path = folder / "seen.state" + notes_path = folder / "prompt-learning.md" + prompt_path.write_text("nadir-angle standing male POV test prompt\n", encoding="utf-8") + image_path.write_bytes(b"fake-png") + + first_scan = subprocess.run( + [ + str(helper_path), + "--folder", + str(folder), + "--target", + "codex:1.0", + "--notes", + str(notes_path), + "--state", + str(state_path), + "--once", + "--dry-run", + ], + cwd=ROOT, + capture_output=True, + text=True, + check=False, + ) + _expect(first_scan.returncode == 0, f"watch helper dry-run scan failed: {first_scan.stderr}") + _expect("tmux send-keys -t codex:1.0" in first_scan.stdout, "watch helper dry-run did not render tmux target") + _expect(str(prompt_path) in first_scan.stdout, "watch helper notification lost prompt path") + _expect(str(image_path) in first_scan.stdout, "watch helper notification lost image path") + _expect(str(notes_path) in first_scan.stdout, "watch helper notification lost notes path") + _expect("atlas_case_001.txt" in state_path.read_text(encoding="utf-8"), "watch helper did not record notified prompt") + + second_scan = subprocess.run( + [ + str(helper_path), + "--folder", + str(folder), + "--target", + "codex:1.0", + "--notes", + str(notes_path), + "--state", + str(state_path), + "--once", + "--dry-run", + ], + cwd=ROOT, + capture_output=True, + text=True, + check=False, + ) + _expect(second_scan.returncode == 0, f"watch helper repeated dry-run scan failed: {second_scan.stderr}") + _expect("no new prompt/image pairs" in second_scan.stdout, "watch helper should dedupe already-notified pairs") + + jpeg_prompt = folder / "atlas_case_002.prompt" + jpeg_image = folder / "atlas_case_002.jpg" + jpeg_prompt.write_text("side-profile oral prompt\n", encoding="utf-8") + jpeg_image.write_bytes(b"fake-jpg") + third_scan = subprocess.run( + [ + str(helper_path), + "--folder", + str(folder), + "--target", + "codex:1.0", + "--notes", + str(notes_path), + "--state", + str(state_path), + "--once", + "--dry-run", + ], + cwd=ROOT, + capture_output=True, + text=True, + check=False, + ) + _expect(third_scan.returncode == 0, f"watch helper jpeg dry-run scan failed: {third_scan.stderr}") + _expect(str(jpeg_prompt) in third_scan.stdout and str(jpeg_image) in third_scan.stdout, "watch helper lost prompt/JPEG pairing") + + def smoke_sxcp_prompt_batch_cli_policy() -> None: helper_path = ROOT / "tools" / "sxcp_prompt_batch.py" eval_loop_doc = (ROOT / "docs" / "sxcp-eval-loop.md").read_text(encoding="utf-8") @@ -12180,6 +12287,7 @@ SMOKE_CASES: list[tuple[str, Callable[[], None]]] = [ ("seed_config_policy", smoke_seed_config_policy), ("prompt_route_simulation_policy", smoke_prompt_route_simulation_policy), ("sxcp_mcp_client_cli_policy", smoke_sxcp_mcp_client_cli_policy), + ("watch_prompt_image_folder_cli_policy", smoke_watch_prompt_image_folder_cli_policy), ("sxcp_prompt_batch_cli_policy", smoke_sxcp_prompt_batch_cli_policy), ("node_camera_registration", smoke_node_camera_registration), ("node_route_config_registration", smoke_node_route_config_registration), diff --git a/tools/watch_prompt_image_folder.sh b/tools/watch_prompt_image_folder.sh new file mode 100755 index 0000000..e74ce15 --- /dev/null +++ b/tools/watch_prompt_image_folder.sh @@ -0,0 +1,248 @@ +#!/usr/bin/env bash +set -euo pipefail + +usage() { + cat <<'EOF' +Usage: + tools/watch_prompt_image_folder.sh --folder DIR --target TMUX_TARGET [options] + +Watch a folder for prompt/image pairs and notify a selected Byobu/tmux Codex +pane using tmux send-keys. Prompt and image files are paired by basename: + atlas_case_001.txt + atlas_case_001.png + atlas_case_002.prompt + atlas_case_002.jpg + +Required: + --folder DIR Folder to watch. + --target TARGET tmux target pane, for example session:1.0. + +Options: + --notes FILE Notes/output file to mention in the Codex message. + Default: DIR/prompt-learning.md + --state FILE Seen-state file. Default: DIR/.sxcp_watch_seen + --once Scan once and exit. + --dry-run Print tmux send-keys commands instead of sending them. + --poll-interval SEC Poll/fallback interval in seconds. Default: 2 + --stable-delay SEC Seconds to wait before accepting a new image. Default: 1 + --prompt-exts CSV Prompt extensions. Default: txt,prompt + --image-exts CSV Image extensions. Default: png,jpg,jpeg,webp + -h, --help Show this help. + +Inside Byobu/tmux, get the current target with: + tmux display-message -p '#S:#I.#P' +EOF +} + +folder="" +target="" +notes="" +state="" +once=0 +dry_run=0 +poll_interval=2 +stable_delay=1 +prompt_exts_csv="txt,prompt" +image_exts_csv="png,jpg,jpeg,webp" + +while [[ $# -gt 0 ]]; do + case "$1" in + --folder) + folder="${2:-}" + shift 2 + ;; + --target) + target="${2:-}" + shift 2 + ;; + --notes) + notes="${2:-}" + shift 2 + ;; + --state) + state="${2:-}" + shift 2 + ;; + --once) + once=1 + shift + ;; + --dry-run) + dry_run=1 + shift + ;; + --poll-interval) + poll_interval="${2:-}" + shift 2 + ;; + --stable-delay) + stable_delay="${2:-}" + shift 2 + ;; + --prompt-exts) + prompt_exts_csv="${2:-}" + shift 2 + ;; + --image-exts) + image_exts_csv="${2:-}" + shift 2 + ;; + -h|--help) + usage + exit 0 + ;; + *) + echo "unknown argument: $1" >&2 + usage >&2 + exit 2 + ;; + esac +done + +if [[ -z "$folder" || -z "$target" ]]; then + echo "--folder and --target are required" >&2 + usage >&2 + exit 2 +fi + +if [[ ! -d "$folder" ]]; then + echo "folder does not exist: $folder" >&2 + exit 2 +fi + +folder="$(cd "$folder" && pwd -P)" +if [[ -z "$notes" ]]; then + notes="$folder/prompt-learning.md" +fi +if [[ -z "$state" ]]; then + state="$folder/.sxcp_watch_seen" +fi +if [[ "$notes" != /* ]]; then + notes="$PWD/$notes" +fi +if [[ "$state" != /* ]]; then + state="$PWD/$state" +fi + +mkdir -p "$(dirname "$state")" +touch "$state" + +IFS=',' read -r -a prompt_exts <<< "$prompt_exts_csv" +IFS=',' read -r -a image_exts <<< "$image_exts_csv" + +shell_quote() { + printf '%q' "$1" +} + +state_key() { + local prompt_path="$1" + local image_path="$2" + printf '%s | %s\n' "$prompt_path" "$image_path" +} + +is_seen() { + local key="$1" + grep -Fxq -- "$key" "$state" +} + +mark_seen() { + local key="$1" + printf '%s\n' "$key" >> "$state" +} + +file_size() { + wc -c < "$1" | tr -d '[:space:]' +} + +wait_for_stable_image() { + local image_path="$1" + if [[ "$dry_run" -eq 1 || "$stable_delay" == "0" ]]; then + return 0 + fi + local before + local after + before="$(file_size "$image_path")" + sleep "$stable_delay" + after="$(file_size "$image_path")" + [[ "$before" == "$after" ]] +} + +find_image_for_prompt() { + local prompt_path="$1" + local filename + local stem + local ext + local candidate + filename="$(basename "$prompt_path")" + stem="${filename%.*}" + for ext in "${image_exts[@]}"; do + candidate="$folder/$stem.$ext" + if [[ -f "$candidate" ]]; then + printf '%s\n' "$candidate" + return 0 + fi + candidate="$folder/$stem.${ext^^}" + if [[ -f "$candidate" ]]; then + printf '%s\n' "$candidate" + return 0 + fi + done + return 1 +} + +notify_codex() { + local prompt_path="$1" + local image_path="$2" + local message + message="New atlas sample ready: prompt=$prompt_path image=$image_path. Analyze it and append prompt-learning notes to $notes." + if [[ "$dry_run" -eq 1 ]]; then + printf 'tmux send-keys -t %s %s Enter\n' "$(shell_quote "$target")" "$(shell_quote "$message")" + return 0 + fi + tmux send-keys -t "$target" "$message" Enter +} + +scan_once() { + local notified=0 + local ext + local prompt_path + local image_path + local key + for ext in "${prompt_exts[@]}"; do + while IFS= read -r -d '' prompt_path; do + if ! image_path="$(find_image_for_prompt "$prompt_path")"; then + continue + fi + key="$(state_key "$prompt_path" "$image_path")" + if is_seen "$key"; then + continue + fi + if ! wait_for_stable_image "$image_path"; then + continue + fi + notify_codex "$prompt_path" "$image_path" + mark_seen "$key" + notified=$((notified + 1)) + done < <(find "$folder" -maxdepth 1 -type f \( -iname "*.$ext" \) -print0) + done + if [[ "$notified" -eq 0 && "$once" -eq 1 ]]; then + echo "no new prompt/image pairs in $folder" + fi + return 0 +} + +if [[ "$once" -eq 1 ]]; then + scan_once + exit 0 +fi + +scan_once +if command -v inotifywait >/dev/null 2>&1; then + while inotifywait -qq -e close_write,create,moved_to "$folder"; do + scan_once + done +else + echo "inotifywait not found; polling $folder every $poll_interval seconds" >&2 + while true; do + sleep "$poll_interval" + scan_once + done +fi