release: camera AI 0.4.9 worker 0.1.5

This commit is contained in:
KINDNICK 2026-08-09 05:10:35 +09:00
parent 9a7e0a3fd0
commit 5a2e95485e
53 changed files with 3933 additions and 584 deletions

View File

@ -1,5 +1,32 @@
# Changelog
## 0.4.9 - 2026-08-09
- Restored the authored same-audio cut prior for production generation inputs
without admitting the target camera trajectory into the planner or template
library. The Arisa regression song now recovers its music-shaped 69-shot
cadence, while a silent-tail fallback cut is removed.
- Replaced performer-screen-motion penalties with stabilized residual framing
diagnostics so expressive dance motion no longer suppresses orbit, drift,
lead-room, or authored camera movement. Hard person, distance, step, aim, and
dynamics gates remain fail-closed.
- Calibrated front-facing safety once over the full performance. Choreography
turns can keep a stable stage-front camera without rewarding side or rear
placement, while true 180-degree rear candidates remain rejected.
- Added a versioned selection-safety contract for selected-shot regeneration,
verified exact-audio reference hashes across bundled and project data roots,
and deterministically deduplicated identical packaged reference copies.
- Added a final-world post-clamp kinematic repair and hard audit. Distance
clamping can no longer reintroduce an unbraked reversal after the YAMO
deceleration/acceleration pass; all 69 regression shots pass the final
post-clamp contract and Shot 66 has zero remaining abrupt reversal events.
- Rebuilt the self-contained Windows worker as 0.1.5 with Python 3.12.13 in a
disposable, exact-pinned environment with one-thread scientific-library
limits, artifact size/file-count guards, and a distribution provenance
manifest. The clean worker contains 647 files (267,608,825 bytes), passes
`doctor`, and is byte-identical to source generation for camera, time, and
shot outputs.
## 0.4.8 - 2026-08-09
- Replaced the unstable frame-local aim controller with a causal, feed-forward

View File

@ -17,7 +17,7 @@ must remain together.
In Unity Package Manager, choose **Add package from git URL** and enter:
```text
https://kindnick-git.duckdns.org/mingle/streamingle-unity-utilities.git?path=/CameraAI~#v0.1.7
https://kindnick-git.duckdns.org/mingle/streamingle-unity-utilities.git?path=/CameraAI~#v0.1.15
```
The initial package download is large because the frozen Windows worker is
@ -26,7 +26,7 @@ it, remove the package lock entry, and add the package again.
## Reference data
Version 0.1.7 includes a compact, read-only `RuntimeData~` bundle with the
Version 0.1.15 includes a compact, read-only `RuntimeData~` bundle with the
263 prepared reference songs, cut policy, and ranker model. An artist
workstation does not need a separate `CW-AI` checkout or Python installation.
If a newer access-controlled library is available, it remains an optional

View File

@ -16,7 +16,7 @@ Cinemachine Track이나 기존 카메라 애니메이션은 필요하지 않으
Unity Package Manager의 `Add package from git URL`에는 다음 주소를 사용할 수
있습니다.
`https://kindnick-git.duckdns.org/mingle/streamingle-unity-utilities.git?path=/CameraAI~#v0.1.7`
`https://kindnick-git.duckdns.org/mingle/streamingle-unity-utilities.git?path=/CameraAI~#v0.1.15`
배포 패키지에는 Windows x64용 `CWCameraWorker` 폴더 전체가 포함됩니다.
Python은 따로 설치하지 않아도 되지만, Git 패키지의 대용량 바이너리를 받으려면
@ -24,10 +24,11 @@ Unity를 열기 전에 Git과 Git LFS를 설치해야 합니다. Worker는 단
아니므로 `CWCameraWorker.exe`와 같은 폴더의 `_internal` 내용을 함께 유지해야
합니다.
카메라 생성에 필요한 사내 참조 카메라·모션 데이터는 권한이 있는 `CW-AI`
데이터 루트에서 읽습니다. 해당 데이터는 이 유틸리티 패키지에 포함되지 않습니다.
다른 PC에서는 `CWAI_ROOT` 환경 변수, Unity 프로젝트 옆의 `CW-AI` 폴더 또는
생성 창의 `고급 · 진단 > 참조 데이터 루트` 중 하나로 위치를 지정합니다.
카메라 생성에 필요한 준비된 참조 카메라·모션 데이터와 컷 모델은 패키지의
읽기 전용 `RuntimeData~`에 포함됩니다. 별도의 `CW-AI` 체크아웃은 필요하지
않습니다. 더 최신의 권한 제어 데이터가 있다면 `CWAI_ROOT` 환경 변수, Unity
프로젝트 옆의 `CW-AI` 폴더 또는 생성 창의
`고급 · 진단 > 참조 데이터 루트`에서 선택적으로 덮어쓸 수 있습니다.
상세 설치 절차는 `Documentation~/EXTERNAL_INSTALLATION.md`를 참고하세요.
참조 데이터는 읽기 전용이어도 됩니다. 반복 생성 캐시는 기본적으로 현재 Unity
프로젝트의 `Library/CWAI`에 저장되며, 필요하면 `CWAI_CACHE_ROOT` 환경 변수로

Binary file not shown.

BIN
CameraAI~/Tools~/CWCameraWorker/_internal/_elementtree.pyd (Stored with Git LFS) Normal file

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@ -1,18 +1,18 @@
{
"schemaVersion": "cw-camera-worker-build-identity-v1",
"workerVersion": "0.1.4",
"createdUtc": "2026-08-08T17:11:19.216870+00:00",
"workerVersion": "0.1.5",
"createdUtc": "2026-08-08T20:03:04.908909+00:00",
"python": "3.12.13",
"sourceRootRelative": "cwai_sources/repository",
"sourceSha256": {
"MachineLearning/CameraDirector/adjacent_transition.py": "6c1b62c2996960af23d62e9c3acd3114600effda03273b006f7076c467c4bdfa",
"MachineLearning/CameraDirector/build_hybrid_preparation_cache.py": "164a3574d5fad05a627bf6d0c6169d42039043bf9a2cd62effbd978359e828f2",
"MachineLearning/CameraDirector/camera_kinematics.py": "c7b85616ef44a0f28f11702459d67d78aad7cf26ed00c2548ecb78b06fc27025",
"MachineLearning/CameraDirector/build_hybrid_preparation_cache.py": "f948b1a68352893b078ac1f6ef458df2107565378eed32890276776ca0dfdad6",
"MachineLearning/CameraDirector/camera_kinematics.py": "75816a55ac1dc655731432f061c2f4356cc3b583f3ce33797578431adcd9ba39",
"MachineLearning/CameraDirector/camera_runtime_data.py": "29fd30a1c97d384f7bb22d024a4f88178f6245858925c302b059a7a1afdeaac1",
"MachineLearning/CameraDirector/cw_camera_cli.py": "943870a4cdec7e4b830b9c945d690e700902cf2a37d1e49987bba85bf0b0ea87",
"MachineLearning/CameraDirector/cw_camera_runtime.py": "94d9f03488978cf34cadbc9495542983c15c8e5050be6d7b9b0317845ca25242",
"MachineLearning/CameraDirector/data_driven_cut_planner.py": "b6d48d03f8725aa8327abe81afa483c43edc368e7dee9f9e7228e21982f71ad2",
"MachineLearning/CameraDirector/generate_hybrid.py": "fbb7bd07fb32f81efd259b4bb56e30404df59eb98fe05c9c92429a214f666659",
"MachineLearning/CameraDirector/cw_camera_runtime.py": "c4cfbc61173cb541932632e3b0a0751ae75c9d6ca3b56680722caa78f74e797d",
"MachineLearning/CameraDirector/data_driven_cut_planner.py": "36cf86a80577278d2e966cb0ebf7c3110b5b1dda5abd1d8a6b0a76e31d10e32b",
"MachineLearning/CameraDirector/generate_hybrid.py": "4bf19ffdd8e244a82b4ea0656bad030a30470a9e2065bdccde8bfb9d99922fea",
"MachineLearning/CameraDirector/hybrid_candidate_cache.py": "a0a5b6a8f612f18cb2875394f17ed89950e1e62e38c1226457d9e35848f6e380",
"MachineLearning/CameraDirector/hybrid_cut_reference.py": "53521d9e31d315c011892961887baf0cde6451020bf39c9d080a5072d4707fcb",
"MachineLearning/CameraDirector/hybrid_preparation_cache.py": "1b0df53124109dee2d744777979a0553999cb276cb6f538a17ddce6516e3da8b",
@ -20,11 +20,11 @@
"MachineLearning/CameraDirector/planner.py": "8575788c0ff1e984f8238354dcd6de2dab9f62afeb71fa4b77ebfc7c66400372",
"MachineLearning/CameraDirector/shot_features.py": "fd912b00320ea53ca682c010de6ed1102edcd021ceb4822a5785033a500786fe",
"MachineLearning/CameraDirector/train.py": "40e9e9a9ab824d596b14f987b2977f187695c3d5a2a2bde9da67e95d38d78836",
"MachineLearning/CameraDirector/trajectory_quality.py": "8ede816e1d155e9d35fd6d18b3aaa145cd4957e205d898e11c1691222ef420a2"
"MachineLearning/CameraDirector/trajectory_quality.py": "20cd2ddc1b2349f8c33f6d1460580f37964a521eaa86ceee5b8ae457512ba49d"
},
"preparationLogicIdentifier": "acba2a46d9d979b013b7154bada6b6a8a7adfd7acd1721876ce6699ed4e03e84",
"candidateLogicIdentifier": "a9503f8764910d763fe0135f6b1425b0cba289dbd1dd149609d2c00e508470c1",
"generationCodeIdentifier": "05e85df5ac7eb614f68e340502cb03a9c5a073f91eba5d16d642340728d51d67",
"candidateLogicIdentifier": "7c771be40d4c6ebc6b15a96c64ed05062f6109d20605670be9b0e07587f2b25a",
"generationCodeIdentifier": "470c77fe5d69046aa7bcadfbbb1337d6f0025fe0be374cb2302ea41ae00cdc47",
"generationCodeFiles": [
"adjacent_transition.py",
"camera_kinematics.py",

View File

@ -260,10 +260,9 @@ def run(args: argparse.Namespace) -> dict:
summary = {
"targetSongId": target_record.id,
"targetDatasetId": target_record.dataset_id,
"targetEvaluationGroupId": (
target_entry["evaluationGroupId"]
if target_entry is not None
else None
"targetEvaluationGroupId": generate_hybrid.target_reference_evaluation_group(
entries,
target_record,
),
"targetTrainingIndexMembership": target_entry is not None,
"targetInputKind": (

View File

@ -19,7 +19,7 @@ import numpy as np
TRANSLATION_KINEMATIC_POLICY_VERSION = (
"yamo-angle-aware-turn-v4-c2-continuity-guard"
"yamo-angle-aware-turn-v6-natural-easing-impulse-c2"
)
DEFAULT_REVERSAL_ANGLE_DEGREES = 40.0
DEFAULT_STOP_SPEED_RATIO = 0.12
@ -44,6 +44,31 @@ DEFAULT_ACCELERATION_GUARD_FLOOR_MPS2 = 25.0
DEFAULT_MAXIMUM_JERK_REGRESSION_RATIO = 1.35
DEFAULT_MAXIMUM_JERK_REGRESSION_DELTA_MPS3 = 350.0
DEFAULT_JERK_GUARD_FLOOR_MPS3 = 350.0
# A second, deliberately narrow gate catches a concentrated turn that evades
# the 40-degree rule yet still carries an edit-visible sideways impulse. The
# thresholds were calibrated against 2,479 authored turn events: the combined
# gate adds only 10 events (0.4%) beyond the existing abrupt-turn policy while
# covering the reviewed Shot_066 profile (high stop ratio plus ~22 m/s^2 and
# ~200 m/s^3 local lateral dynamics).
DEFAULT_IMPULSIVE_TURN_ANGLE_DEGREES = 15.0
DEFAULT_IMPULSIVE_STOP_SPEED_RATIO = 0.78
DEFAULT_IMPULSIVE_CORNER_CONCENTRATION = 0.40
DEFAULT_IMPULSIVE_LATERAL_ACCELERATION_MPS2 = 22.0
DEFAULT_IMPULSIVE_LATERAL_JERK_MPS3 = 200.0
DEFAULT_IMPULSIVE_TARGET_SPEED_RATIO = 0.72
DEFAULT_IMPULSIVE_REVERSAL_WINDOW_SECONDS = 0.50
# A generated curve can satisfy the broad monotonic-speed definition of
# ``natural_easing`` while its turn is still concentrated enough to read as a
# lateral kick. Keep this as a separate, narrower contradiction gate rather
# than weakening the micro-turn thresholds above. The reviewed Shot_066 is
# 59.60 degrees / 0.812 stop ratio / 0.355 concentration / 22.69 m/s^2 /
# 167.84 m/s^3. A scan of 1,395 detected events across 2,006 authored shots
# found only three grouped events satisfying every condition below (0.22%);
# all three also carried substantially larger 645--958 m/s^3 lateral jerk.
DEFAULT_NATURAL_IMPULSE_TARGET_EXCESS_RATIO = 0.08
DEFAULT_NATURAL_IMPULSE_CORNER_CONCENTRATION = 0.35
DEFAULT_NATURAL_IMPULSE_LATERAL_ACCELERATION_MPS2 = 22.0
DEFAULT_NATURAL_IMPULSE_LATERAL_JERK_MPS3 = 160.0
def turn_speed_ratio_limit(angle_degrees: float) -> float:
@ -99,9 +124,16 @@ class TranslationReversal:
target_speed_ratio: float
corner_concentration: float
natural_easing_detected: bool
impulsive: bool = False
lateral_acceleration_mps2: float = 0.0
lateral_jerk_mps3: float = 0.0
@property
def abrupt(self) -> bool:
if self.impulsive:
return self.stop_speed_ratio > (
self.target_speed_ratio + DEFAULT_SPEED_RATIO_TOLERANCE
)
if self.natural_easing_detected and self.stop_speed_ratio <= (
natural_turn_speed_ratio_limit(self.angle_degrees)
+ DEFAULT_SPEED_RATIO_TOLERANCE
@ -184,16 +216,71 @@ def _group_reversal_candidates(
group,
key=lambda event: (event.stop_speed_ratio, event.frame),
)
impulsive_events = [event for event in group if event.impulsive]
representative = (
max(
impulsive_events,
key=lambda event: (
event.lateral_jerk_mps3,
event.lateral_acceleration_mps2,
-event.frame,
),
)
if impulsive_events
else low_speed_event
)
maximum_angle = max(event.angle_degrees for event in group)
maximum_concentration = max(
event.corner_concentration for event in group
)
maximum_lateral_acceleration = max(
event.lateral_acceleration_mps2 for event in group
)
maximum_lateral_jerk = max(
event.lateral_jerk_mps3 for event in group
)
maximum_angle_target = turn_speed_ratio_limit(maximum_angle)
# A detector group represents one physical corner, but its clearest
# slowdown, largest angle and largest lateral impulse commonly occur
# on neighbouring centers. Evaluate this contradiction after
# grouping; requiring every signal on one center missed Shot_066 even
# though the aggregate event was an edit-visible kick.
natural_easing_impulse = bool(
low_speed_event.natural_easing_detected
and maximum_angle
>= DEFAULT_REVERSAL_ANGLE_DEGREES
- DEFAULT_ANGLE_COMPARISON_TOLERANCE_DEGREES
and low_speed_event.stop_speed_ratio
>= DEFAULT_IMPULSIVE_STOP_SPEED_RATIO
and low_speed_event.stop_speed_ratio - maximum_angle_target
>= DEFAULT_NATURAL_IMPULSE_TARGET_EXCESS_RATIO
and maximum_concentration
>= DEFAULT_NATURAL_IMPULSE_CORNER_CONCENTRATION
and maximum_lateral_acceleration
>= DEFAULT_NATURAL_IMPULSE_LATERAL_ACCELERATION_MPS2
and maximum_lateral_jerk
>= DEFAULT_NATURAL_IMPULSE_LATERAL_JERK_MPS3
)
impulsive = bool(impulsive_events) or natural_easing_impulse
events.append(
TranslationReversal(
frame=low_speed_event.frame,
frame=representative.frame,
angle_degrees=maximum_angle,
stop_speed_ratio=low_speed_event.stop_speed_ratio,
flank_speed_mps=low_speed_event.flank_speed_mps,
target_speed_ratio=turn_speed_ratio_limit(maximum_angle),
corner_concentration=max(event.corner_concentration for event in group),
natural_easing_detected=(low_speed_event.natural_easing_detected),
stop_speed_ratio=representative.stop_speed_ratio,
flank_speed_mps=representative.flank_speed_mps,
target_speed_ratio=(
min(
maximum_angle_target,
DEFAULT_IMPULSIVE_TARGET_SPEED_RATIO,
)
if impulsive
else maximum_angle_target
),
corner_concentration=maximum_concentration,
natural_easing_detected=(representative.natural_easing_detected),
impulsive=impulsive,
lateral_acceleration_mps2=maximum_lateral_acceleration,
lateral_jerk_mps3=maximum_lateral_jerk,
)
)
return events
@ -364,9 +451,73 @@ def analyze_translational_reversals(
)
valid &= radius[centers] >= 0.1
# Keep the original, angle-aware policy result separate. The impulsive
# gate below is a narrow fallback for turns that the established policy
# would otherwise preserve; it must not strengthen the slowdown already
# selected for an ordinary 40+ degree corner.
standard_valid = valid.copy()
local_speed_windows = np.lib.stride_tricks.sliding_window_view(speed, 4)
local_minimum_speeds = np.min(local_speed_windows[centers - 2], axis=1)
ratios = local_minimum_speeds / np.maximum(flank_speeds, 1e-12)
lateral_acceleration_peaks = np.zeros(len(centers), dtype=np.float64)
lateral_jerk_peaks = np.zeros(len(centers), dtype=np.float64)
if family != "orbit":
acceleration = np.diff(velocity, axis=0) * sample_rate
jerk = np.diff(acceleration, axis=0) * sample_rate
for result_index, center in enumerate(centers):
incoming = before_vectors[result_index]
incoming_norm = float(np.linalg.norm(incoming))
if incoming_norm <= 1e-12:
continue
incoming_unit = incoming / incoming_norm
window_start = max(0, int(center) - persistence_frames)
acceleration_window = acceleration[
window_start : min(len(acceleration), int(center) + persistence_frames)
]
jerk_window = jerk[
window_start : min(len(jerk), int(center) + persistence_frames)
]
if len(acceleration_window):
lateral = acceleration_window - (
acceleration_window @ incoming_unit
)[:, None] * incoming_unit
lateral_acceleration_peaks[result_index] = float(
np.max(np.linalg.norm(lateral, axis=1))
)
if len(jerk_window):
lateral = jerk_window - (
jerk_window @ incoming_unit
)[:, None] * incoming_unit
lateral_jerk_peaks[result_index] = float(
np.max(np.linalg.norm(lateral, axis=1))
)
impulsive_valid = (
(flank_speeds >= minimum_flank_speed_mps)
& (
angles
>= DEFAULT_IMPULSIVE_TURN_ANGLE_DEGREES
- DEFAULT_ANGLE_COMPARISON_TOLERANCE_DEGREES
)
& (before_coherence >= minimum_flank_direction_coherence)
& (after_coherence >= minimum_flank_direction_coherence)
& (
corner_concentration
>= max(
minimum_corner_concentration,
DEFAULT_IMPULSIVE_CORNER_CONCENTRATION,
)
)
& (np.minimum(before_travel, after_travel) >= minimum_flank_travel_meters)
& (ratios >= DEFAULT_IMPULSIVE_STOP_SPEED_RATIO)
& (
lateral_acceleration_peaks
>= DEFAULT_IMPULSIVE_LATERAL_ACCELERATION_MPS2
)
& (lateral_jerk_peaks >= DEFAULT_IMPULSIVE_LATERAL_JERK_MPS3)
)
else:
impulsive_valid = np.zeros(len(centers), dtype=bool)
persistence_speed_windows = np.lib.stride_tricks.sliding_window_view(
speed,
persistence_frames,
@ -393,6 +544,30 @@ def analyze_translational_reversals(
& (after_monotonic_ratio >= 0.75)
& (ratios <= natural_speed_limits + DEFAULT_SPEED_RATIO_TOLERANCE)
)
standard_speed_limits = np.asarray(
[turn_speed_ratio_limit(float(angle)) for angle in angles],
dtype=np.float64,
)
standard_abrupt = standard_valid & ~(
natural_easing
& (ratios <= natural_speed_limits + DEFAULT_SPEED_RATIO_TOLERANCE)
) & (ratios > standard_speed_limits + DEFAULT_SPEED_RATIO_TOLERANCE)
# Only upgrade events that evade the established abrupt-turn rule. This
# keeps authored 40--180 degree slowdown curves on their calibrated policy
# while admitting the reviewed concentrated, high-jerk micro-turn class.
impulsive_valid &= ~standard_abrupt
if np.any(standard_abrupt):
# Adjacent detector centers represent one physical corner and are
# grouped later. Clear the fallback throughout that grouping radius,
# otherwise a nearby 39.9-degree center could relabel an ordinary
# 40-degree event as impulsive and incorrectly strengthen its target.
standard_abrupt_centers = centers[standard_abrupt]
impulsive_valid &= ~np.any(
np.abs(centers[:, None] - standard_abrupt_centers[None, :])
<= persistence_frames,
axis=1,
)
valid |= impulsive_valid
candidates = [
TranslationReversal(
frame=int(frame),
@ -402,6 +577,9 @@ def analyze_translational_reversals(
target_speed_ratio=turn_speed_ratio_limit(float(angle)),
corner_concentration=float(concentration),
natural_easing_detected=bool(is_naturally_eased),
impulsive=bool(is_impulsive),
lateral_acceleration_mps2=float(lateral_acceleration),
lateral_jerk_mps3=float(lateral_jerk),
)
for (
frame,
@ -410,6 +588,9 @@ def analyze_translational_reversals(
flank_speed,
concentration,
is_naturally_eased,
is_impulsive,
lateral_acceleration,
lateral_jerk,
) in zip(
centers[valid],
angles[valid],
@ -417,6 +598,9 @@ def analyze_translational_reversals(
flank_speeds[valid],
corner_concentration[valid],
natural_easing[valid],
impulsive_valid[valid],
lateral_acceleration_peaks[valid],
lateral_jerk_peaks[valid],
)
]
@ -611,14 +795,27 @@ def _dynamics_regression_exceeds_guard(
def _event_metadata(events: list[TranslationReversal]) -> dict[str, object]:
abrupt = [event for event in events if event.abrupt]
impulsive = [event for event in events if event.impulsive]
return {
"count": len(events),
"abruptCount": len(abrupt),
"frames": [event.frame for event in events],
"abruptFrames": [event.frame for event in abrupt],
"anglesDegrees": [event.angle_degrees for event in events],
"stopSpeedRatios": [event.stop_speed_ratio for event in events],
"targetSpeedRatios": [event.target_speed_ratio for event in events],
"cornerConcentrations": [event.corner_concentration for event in events],
"naturalEasingDetected": [
event.natural_easing_detected for event in events
],
"impulsiveCount": len(impulsive),
"impulsiveFrames": [event.frame for event in impulsive],
"lateralAccelerationMetersPerSecondSquared": [
event.lateral_acceleration_mps2 for event in events
],
"lateralJerkMetersPerSecondCubed": [
event.lateral_jerk_mps3 for event in events
],
"worstStopSpeedRatio": (
float(max(event.stop_speed_ratio for event in events)) if events else 0.0
),
@ -641,12 +838,15 @@ def regularize_translational_reversals(
*,
sample_rate: float = 60.0,
reversal_window_seconds: float = DEFAULT_REVERSAL_WINDOW_SECONDS,
apply_policy: bool = True,
) -> tuple[np.ndarray, dict[str, object]]:
"""Apply angle-aware braking to abrupt turns and orbit reversals."""
positions = _validated_positions(positions_meters)
if not math.isfinite(reversal_window_seconds) or reversal_window_seconds <= 0.0:
raise ValueError("reversal_window_seconds must be finite and positive.")
if not isinstance(apply_policy, bool):
raise ValueError("apply_policy must be a boolean.")
family = motion_family(motion_type)
before = analyze_translational_reversals(
positions,
@ -658,21 +858,37 @@ def regularize_translational_reversals(
applied_frames: list[int] = []
applied_angles: list[float] = []
applied_target_speed_ratios: list[float] = []
applied_impulsive_frames: list[int] = []
pass_count = 0
pending_events = abrupt_before
pending_events = abrupt_before if apply_policy else []
if family not in {"static", "unsupported"} and pending_events:
requested_radius = max(
6,
int(round(reversal_window_seconds * sample_rate)),
)
impulsive_requested_radius = max(
requested_radius,
int(
round(
DEFAULT_IMPULSIVE_REVERSAL_WINDOW_SECONDS * sample_rate
)
),
)
# Only abrupt events detected on the untouched input are eligible.
# Re-detecting and rewriting new window-boundary events caused a
# three-pass cascade in real shots, multiplying acceleration and jerk
# even though the final turn counter eventually reached zero.
event_frames = [event.frame for event in pending_events]
for event_index, event in enumerate(pending_events):
neighbor_limit = requested_radius
# The high-impulse fallback is intentionally rare, so give its
# braking envelope a slightly longer C2 runway instead of forcing
# a stronger speed change into the normal 0.35-second window.
neighbor_limit = (
impulsive_requested_radius
if event.impulsive
else requested_radius
)
if event_index > 0:
neighbor_limit = min(
neighbor_limit,
@ -700,6 +916,8 @@ def regularize_translational_reversals(
applied_frames.append(event.frame)
applied_angles.append(event.angle_degrees)
applied_target_speed_ratios.append(event.target_speed_ratio)
if event.impulsive:
applied_impulsive_frames.append(event.frame)
pass_count = int(bool(applied_frames))
endpoint_adjustment = output[-1] - positions[-1]
@ -734,6 +952,7 @@ def regularize_translational_reversals(
applied_frames.clear()
applied_angles.clear()
applied_target_speed_ratios.clear()
applied_impulsive_frames.clear()
pass_count = 0
endpoint_adjustment_clamped = False
@ -753,6 +972,7 @@ def regularize_translational_reversals(
"translationKinematicPolicyVersion": (TRANSLATION_KINEMATIC_POLICY_VERSION),
"translationKinematicMotionFamily": family,
"translationKinematicPolicyEligible": family not in {"static", "unsupported"},
"translationKinematicPolicyApplicationEnabled": apply_policy,
"translationKinematicPolicyApplied": bool(applied_frames),
"translationKinematicDynamicsGuardTriggered": dynamics_guard_triggered,
"translationKinematicAttemptedRegularizationCount": len(
@ -765,16 +985,71 @@ def regularize_translational_reversals(
"translationAbruptReversalCountBefore": before_metadata["abruptCount"],
"translationDirectionReversalFramesBefore": before_metadata["frames"],
"translationAbruptReversalFramesBefore": before_metadata["abruptFrames"],
"translationDirectionReversalAnglesDegreesBefore": before_metadata[
"anglesDegrees"
],
"translationDirectionReversalStopSpeedRatiosBefore": before_metadata[
"stopSpeedRatios"
],
"translationDirectionReversalTargetSpeedRatiosBefore": before_metadata[
"targetSpeedRatios"
],
"translationDirectionReversalCornerConcentrationsBefore": before_metadata[
"cornerConcentrations"
],
"translationDirectionReversalNaturalEasingDetectedBefore": before_metadata[
"naturalEasingDetected"
],
"translationDirectionReversalLateralAccelerationMetersPerSecondSquaredBefore": (
before_metadata["lateralAccelerationMetersPerSecondSquared"]
),
"translationDirectionReversalLateralJerkMetersPerSecondCubedBefore": (
before_metadata["lateralJerkMetersPerSecondCubed"]
),
"translationWorstStopSpeedRatioBefore": before_metadata["worstStopSpeedRatio"],
"translationRegularizedReversalCount": len(applied_frames),
"translationKinematicRegularizationPassCount": pass_count,
"translationRegularizedReversalFrames": applied_frames,
"translationRegularizedTurnAnglesDegrees": applied_angles,
"translationRegularizedTargetSpeedRatios": (applied_target_speed_ratios),
"translationImpulsiveTurnCountBefore": before_metadata[
"impulsiveCount"
],
"translationImpulsiveTurnFramesBefore": before_metadata[
"impulsiveFrames"
],
"translationImpulsiveTurnRegularizedFrames": applied_impulsive_frames,
"translationImpulsiveTurnCountAfter": after_metadata[
"impulsiveCount"
],
"translationImpulsiveTurnFramesAfter": after_metadata[
"impulsiveFrames"
],
"translationDirectionReversalCountAfter": after_metadata["count"],
"translationAbruptReversalCountAfter": after_metadata["abruptCount"],
"translationDirectionReversalFramesAfter": after_metadata["frames"],
"translationAbruptReversalFramesAfter": after_metadata["abruptFrames"],
"translationDirectionReversalAnglesDegreesAfter": after_metadata[
"anglesDegrees"
],
"translationDirectionReversalStopSpeedRatiosAfter": after_metadata[
"stopSpeedRatios"
],
"translationDirectionReversalTargetSpeedRatiosAfter": after_metadata[
"targetSpeedRatios"
],
"translationDirectionReversalCornerConcentrationsAfter": after_metadata[
"cornerConcentrations"
],
"translationDirectionReversalNaturalEasingDetectedAfter": after_metadata[
"naturalEasingDetected"
],
"translationDirectionReversalLateralAccelerationMetersPerSecondSquaredAfter": (
after_metadata["lateralAccelerationMetersPerSecondSquared"]
),
"translationDirectionReversalLateralJerkMetersPerSecondCubedAfter": (
after_metadata["lateralJerkMetersPerSecondCubed"]
),
"translationWorstStopSpeedRatioAfter": after_metadata["worstStopSpeedRatio"],
"translationWorstTargetSpeedRatioExcessAfter": after_metadata[
"worstTargetSpeedRatioExcess"
@ -782,6 +1057,41 @@ def regularize_translational_reversals(
"translationStopSpeedRatioLimit": DEFAULT_STOP_SPEED_RATIO,
"translationReversalAngleMinimumDegrees": (DEFAULT_REVERSAL_ANGLE_DEGREES),
"translationMinimumCornerConcentration": (DEFAULT_MINIMUM_CORNER_CONCENTRATION),
"translationImpulsiveTurnPolicy": {
"minimumAngleDegrees": DEFAULT_IMPULSIVE_TURN_ANGLE_DEGREES,
"minimumStopSpeedRatio": DEFAULT_IMPULSIVE_STOP_SPEED_RATIO,
"minimumCornerConcentration": (
DEFAULT_IMPULSIVE_CORNER_CONCENTRATION
),
"minimumLateralAccelerationMetersPerSecondSquared": (
DEFAULT_IMPULSIVE_LATERAL_ACCELERATION_MPS2
),
"minimumLateralJerkMetersPerSecondCubed": (
DEFAULT_IMPULSIVE_LATERAL_JERK_MPS3
),
"maximumTargetSpeedRatio": DEFAULT_IMPULSIVE_TARGET_SPEED_RATIO,
"decelerationAndAccelerationWindowSeconds": (
DEFAULT_IMPULSIVE_REVERSAL_WINDOW_SECONDS
),
"naturalEasingContradiction": {
"minimumOriginalTurnAngleDegrees": (
DEFAULT_REVERSAL_ANGLE_DEGREES
),
"minimumTargetSpeedRatioExcess": (
DEFAULT_NATURAL_IMPULSE_TARGET_EXCESS_RATIO
),
"minimumCornerConcentration": (
DEFAULT_NATURAL_IMPULSE_CORNER_CONCENTRATION
),
"minimumLateralAccelerationMetersPerSecondSquared": (
DEFAULT_NATURAL_IMPULSE_LATERAL_ACCELERATION_MPS2
),
"minimumLateralJerkMetersPerSecondCubed": (
DEFAULT_NATURAL_IMPULSE_LATERAL_JERK_MPS3
),
},
"continuousOrbitExcluded": True,
},
"translationSpeedMetersPerSecondMaxBefore": dynamics_before[
"speedMetersPerSecondMax"
],

View File

@ -19,7 +19,7 @@ from typing import Any, Iterable
BUILD_IDENTITY_SCHEMA_VERSION = "cw-camera-worker-build-identity-v1"
BUILD_IDENTITY_FILE = "cw_camera_worker_build_identity.json"
WORKER_VERSION = "0.1.4"
WORKER_VERSION = "0.1.5"
def is_frozen_runtime() -> bool:

View File

@ -508,6 +508,14 @@ class CutPlannerConfig:
preferred_bonus: float = 2.4
same_audio_bonus: float = 2.8
history_beam_width: int = 4
# Authored timelines commonly hold their final camera through a long
# silent/idle export tail. Do not manufacture a fallback cut merely to
# satisfy the normal maximum shot duration once every causal activity
# signal has been inactive for at least two seconds. The threshold is
# intentionally near zero: this is an export-tail exception, not a way to
# lengthen quiet-but-active musical passages.
inactive_tail_minimum_frames: int = 120
inactive_tail_signal_threshold: float = 1e-6
def __post_init__(self) -> None:
integer_names = (
@ -518,6 +526,7 @@ class CutPlannerConfig:
"ultrashort_frames",
"short_tail_frames",
"history_beam_width",
"inactive_tail_minimum_frames",
)
for name in integer_names:
value = getattr(self, name)
@ -546,6 +555,7 @@ class CutPlannerConfig:
"short_tail_cost",
"preferred_bonus",
"same_audio_bonus",
"inactive_tail_signal_threshold",
):
value = float(getattr(self, name))
if not math.isfinite(value) or value < 0.0:
@ -760,6 +770,41 @@ def _robust_unit(values: np.ndarray) -> np.ndarray:
return np.clip((values - low) / (high - low), 0.0, 1.0)
def _inactive_tail_start(
signals: CutSignals,
config: CutPlannerConfig,
) -> int | None:
"""Return the first frame of a sufficiently long causal inactive suffix.
The suffix must contain no normalized onset, mel-novelty, performer-motion,
beat, or onset-peak evidence above a near-zero threshold. Requiring an
earlier active frame prevents an entirely empty/new input from silently
disabling the normal maximum-shot contract.
"""
activity = np.maximum.reduce(
(
_robust_unit(signals.onset_strength),
_robust_unit(signals.motion_strength),
_robust_unit(signals.mel_novelty),
)
)
if len(signals.beat_frames):
activity[np.asarray(signals.beat_frames, dtype=np.int64)] = 1.0
if len(signals.onset_peak_frames):
activity[np.asarray(signals.onset_peak_frames, dtype=np.int64)] = 1.0
active = np.flatnonzero(activity > config.inactive_tail_signal_threshold)
if not len(active):
return None
start = int(active[-1]) + 1
if (
start <= 0
or signals.frame_count - start < config.inactive_tail_minimum_frames
):
return None
return start
def _salient_peaks(
values: np.ndarray,
quantile: float,
@ -1546,6 +1591,15 @@ def _solve_global_dag(
music_prefix = np.concatenate(([0.0], np.cumsum(music)))
motion_prefix = np.concatenate(([0.0], np.cumsum(motion)))
beat_proximity = _proximity(frames, signals.beat_frames, 30)
inactive_tail_start = _inactive_tail_start(signals, config)
protected_tail_frames = frozenset(
int(frame)
for frame in (
*directives.locked_frames,
*directives.preferred_frames,
*same_audio.tolist(),
)
)
q25 = _weighted_quantile(prior.duration_frames, prior.sample_weights, 0.25)
q75 = _weighted_quantile(prior.duration_frames, prior.sample_weights, 0.75)
density_scale = {
@ -1591,6 +1645,29 @@ def _solve_global_dag(
destination_indices = np.arange(
start_index + 1, min(candidate_count, end_limit), dtype=np.int64
)
endpoint_index = candidate_count - 1
endpoint_beyond_normal_limit = endpoint_index not in destination_indices
active_prefix_frames = (
max(0, inactive_tail_start - start_frame)
if inactive_tail_start is not None
else signals.frame_count
)
crosses_protected_tail_boundary = any(
start_frame < frame < signals.frame_count
for frame in protected_tail_frames
)
inactive_tail_extension_allowed = bool(
inactive_tail_start is not None
and endpoint_beyond_normal_limit
and (
start_frame <= inactive_tail_start
or start_frame in protected_tail_frames
)
and active_prefix_frames <= config.maximum_shot_frames
and not crosses_protected_tail_boundary
)
if inactive_tail_extension_allowed:
destination_indices = np.append(destination_indices, endpoint_index)
if next_lock is not None:
destination_indices = destination_indices[
frames[destination_indices] <= next_lock
@ -1606,23 +1683,41 @@ def _solve_global_dag(
continue
end_frames = frames[destination_indices]
inactive_tail_extension_mask = (
(end_frames == signals.frame_count)
& (end_frames - start_frame > config.maximum_shot_frames)
& inactive_tail_extension_allowed
)
scoring_durations = durations.copy()
if np.any(inactive_tail_extension_mask):
scoring_durations[inactive_tail_extension_mask] = max(
config.minimum_shot_frames,
active_prefix_frames,
)
scoring_end_frames = end_frames.copy()
if np.any(inactive_tail_extension_mask):
scoring_end_frames[inactive_tail_extension_mask] = max(
start_frame + config.minimum_shot_frames,
int(inactive_tail_start),
)
segment_intensities = (
intensity_prefix[end_frames] - intensity_prefix[start_frame]
) / durations
intensity_prefix[scoring_end_frames] - intensity_prefix[start_frame]
) / scoring_durations
segment_music = (
music_prefix[end_frames] - music_prefix[start_frame]
) / durations
music_prefix[scoring_end_frames] - music_prefix[start_frame]
) / scoring_durations
segment_motion = (
motion_prefix[end_frames] - motion_prefix[start_frame]
) / durations
motion_prefix[scoring_end_frames] - motion_prefix[start_frame]
) / scoring_durations
target_durations = (
q75 * (1.0 - segment_intensities) + q25 * segment_intensities
) * density_scale
duration_costs = -config.duration_prior_weight * duration_log_score[
durations - 1
scoring_durations - 1
]
density_costs = config.density_weight * np.square(
(durations - target_durations) / np.maximum(target_durations, 1.0)
(scoring_durations - target_durations)
/ np.maximum(target_durations, 1.0)
)
for state in tuple(sorted(start_states, key=_beam_state_key)):
@ -1720,6 +1815,15 @@ def _solve_global_dag(
"startFrame": start_frame,
"endFrame": end_frame,
"durationFrames": duration,
"durationScoringFrames": int(scoring_durations[offset]),
"inactiveTailContinuation": bool(
inactive_tail_extension_mask[offset]
),
"inactiveTailStartFrame": (
int(inactive_tail_start)
if inactive_tail_extension_mask[offset]
else None
),
"candidateReward": candidate_reward,
"heuristicCandidateScore": (
0.0 if is_endpoint else float(heuristic[end_index])
@ -1855,6 +1959,16 @@ def _solve_global_dag(
"statesExpanded": states_expanded,
"statesPruned": states_pruned,
},
"inactiveTail": {
"detected": inactive_tail_start is not None,
"startFrame": inactive_tail_start,
"minimumFrames": config.inactive_tail_minimum_frames,
"signalThreshold": config.inactive_tail_signal_threshold,
"continuationSelected": any(
bool(edge.get("inactiveTailContinuation", False))
for edge in selected_edges
),
},
"selectedCuts": selected_cuts,
"selectedEdges": selected_edges,
"objectiveTotals": objective_totals,
@ -1874,6 +1988,11 @@ def _solve_global_dag(
else None
),
"sameAudioBoundaryCount": len(same_audio),
"inactiveTailStartFrame": inactive_tail_start,
"inactiveTailContinuationSelected": any(
bool(edge.get("inactiveTailContinuation", False))
for edge in selected_edges
),
"durationPrior": _duration_prior_summary(prior),
"plannerConfig": {
name: getattr(config, name) for name in config.__dataclass_fields__

View File

@ -17,7 +17,18 @@ import numpy as np
TRAJECTORY_QUALITY_VERSION = "trajectory-quality-v2"
C2_RESAMPLING_POLICY_VERSION = "clamped-cubic-c2-endpoint-speed-guard-v1"
RADIAL_CLAMP_POLICY_VERSION = "quintic-bound-identity-c2-speed-adaptive-v2"
DYNAMICS_POLICY_VERSION = "translation-screen-space-cost-hard-gate-v2"
DYNAMICS_POLICY_VERSION = (
"camera-motion-stabilized-screen-residual-cost-hard-gate-v3"
)
RAW_SUBJECT_SCREEN_INPUT = "raw_subject_uv"
STABILIZED_SCREEN_RESIDUAL_INPUT = "stabilized_residual_uv"
SCREEN_INPUT_KINDS = frozenset(
{
RAW_SUBJECT_SCREEN_INPUT,
STABILIZED_SCREEN_RESIDUAL_INPUT,
}
)
@dataclass(frozen=True)
@ -43,7 +54,14 @@ class ResamplingGuardPolicy:
@dataclass(frozen=True)
class CandidateDynamicsPolicy:
"""Transparent scales and weights for a candidate dynamics cost."""
"""Transparent scales, free thresholds, and weights for soft ranking.
The free thresholds represent authored-safe motion that should not change
candidate ordering. Only dynamics above a threshold contribute to the
soft cost. Raw subject screen positions are diagnostic-only because they
contain performer motion; screen acceleration and jerk are scored only
when the caller explicitly supplies a stabilized semantic-target residual.
"""
translation_speed_p99_scale: float = 4.0
translation_acceleration_p99_scale: float = 12.0
@ -53,14 +71,25 @@ class CandidateDynamicsPolicy:
screen_acceleration_p99_scale: float = 12.0
screen_jerk_p99_scale: float = 300.0
screen_horizontal_reversal_count_scale: float = 3.0
translation_speed_p99_free_threshold: float = 2.0
translation_acceleration_p99_free_threshold: float = 6.0
translation_jerk_p99_free_threshold: float = 120.0
lateral_reversal_count_free_threshold: float = 1.0
screen_speed_p99_free_threshold: float = 0.0
screen_acceleration_p99_free_threshold: float = 6.0
screen_jerk_p99_free_threshold: float = 180.0
screen_horizontal_reversal_count_free_threshold: float = 0.0
translation_speed_weight: float = 0.10
translation_acceleration_weight: float = 0.75
translation_jerk_weight: float = 1.00
lateral_reversal_weight: float = 0.60
screen_speed_weight: float = 0.15
# Raw subject speed and reversal count mostly describe choreography, not a
# camera-body jolt. Keep both metrics in the audit payload but exclude
# them from the default soft score even for a residual input.
screen_speed_weight: float = 0.0
screen_acceleration_weight: float = 0.90
screen_jerk_weight: float = 1.20
screen_horizontal_reversal_weight: float = 0.80
screen_horizontal_reversal_weight: float = 0.0
maximum_component_ratio: float = 8.0
maximum_acceptable_cost: float = 6.0
@ -73,6 +102,8 @@ class CandidateDynamicsPolicy:
raise ValueError(f"{name} must be positive")
if name.endswith("_weight") and value < 0.0:
raise ValueError(f"{name} must be non-negative")
if name.endswith("_free_threshold") and value < 0.0:
raise ValueError(f"{name} must be non-negative")
if self.maximum_component_ratio <= 0.0:
raise ValueError("maximum_component_ratio must be positive")
if self.maximum_acceptable_cost < 0.0:
@ -910,16 +941,27 @@ def candidate_dynamics_cost(
screen_space_metrics: Mapping[str, object] | None = None,
*,
policy: CandidateDynamicsPolicy | None = None,
screen_input_kind: str = RAW_SUBJECT_SCREEN_INPUT,
) -> tuple[float, dict[str, object]]:
"""Return a transparent dynamics cost for a generated camera candidate.
High-frequency acceleration, jerk, and persistent lateral reversals receive
more weight than steady speed. This allows deliberate low-frequency camera
travel while making left/right chatter expensive.
High-frequency camera acceleration and jerk receive more weight than
authored-safe low-frequency travel. ``screen_space_metrics`` is scored
only when ``screen_input_kind`` is ``stabilized_residual_uv``. That input
must be the projected semantic focus minus its stabilized desired UV path,
not the raw projected performer position. Raw subject UV remains in the
returned audit but contributes no soft cost, so choreography is not
mistaken for camera shake. This function does not change any downstream
hard safety gate or the metrics those gates consume.
"""
selected_policy = policy or CandidateDynamicsPolicy()
selected_policy.validate()
if screen_input_kind not in SCREEN_INPUT_KINDS:
raise ValueError(
"screen_input_kind must be raw_subject_uv or "
"stabilized_residual_uv"
)
raw_components = [
(
"translationSpeedP99",
@ -930,6 +972,8 @@ def candidate_dynamics_cost(
),
selected_policy.translation_speed_p99_scale,
selected_policy.translation_speed_weight,
selected_policy.translation_speed_p99_free_threshold,
False,
),
(
"translationAccelerationP99",
@ -940,6 +984,8 @@ def candidate_dynamics_cost(
),
selected_policy.translation_acceleration_p99_scale,
selected_policy.translation_acceleration_weight,
selected_policy.translation_acceleration_p99_free_threshold,
False,
),
(
"translationJerkP99",
@ -950,12 +996,16 @@ def candidate_dynamics_cost(
),
selected_policy.translation_jerk_p99_scale,
selected_policy.translation_jerk_weight,
selected_policy.translation_jerk_p99_free_threshold,
False,
),
(
"persistentLateralReversalCount",
float(translation_metrics.get("persistentLateralReversalCount", 0)),
selected_policy.lateral_reversal_count_scale,
selected_policy.lateral_reversal_weight,
selected_policy.lateral_reversal_count_free_threshold,
False,
),
]
if screen_space_metrics is not None:
@ -970,6 +1020,8 @@ def candidate_dynamics_cost(
),
selected_policy.screen_speed_p99_scale,
selected_policy.screen_speed_weight,
selected_policy.screen_speed_p99_free_threshold,
True,
),
(
"screenAccelerationP99",
@ -980,6 +1032,8 @@ def candidate_dynamics_cost(
),
selected_policy.screen_acceleration_p99_scale,
selected_policy.screen_acceleration_weight,
selected_policy.screen_acceleration_p99_free_threshold,
True,
),
(
"screenJerkP99",
@ -990,6 +1044,8 @@ def candidate_dynamics_cost(
),
selected_policy.screen_jerk_p99_scale,
selected_policy.screen_jerk_weight,
selected_policy.screen_jerk_p99_free_threshold,
True,
),
(
"persistentHorizontalReversalCount",
@ -1001,23 +1057,46 @@ def candidate_dynamics_cost(
),
selected_policy.screen_horizontal_reversal_count_scale,
selected_policy.screen_horizontal_reversal_weight,
(
selected_policy
.screen_horizontal_reversal_count_free_threshold
),
True,
),
]
)
components: dict[str, dict[str, float]] = {}
components: dict[str, dict[str, float | bool]] = {}
total_cost = 0.0
for name, value, scale, weight in raw_components:
score_screen_residual = (
screen_input_kind == STABILIZED_SCREEN_RESIDUAL_INPUT
)
for name, value, scale, configured_weight, free_threshold, is_screen in (
raw_components
):
if not np.isfinite(value) or value < 0.0:
raise ValueError(f"candidate dynamics component {name} is invalid")
normalized = min(value / scale, selected_policy.maximum_component_ratio)
weight = (
configured_weight
if not is_screen or score_screen_residual
else 0.0
)
excess = max(0.0, value - free_threshold)
normalized = min(
excess / scale,
selected_policy.maximum_component_ratio,
)
contribution = weight * normalized**2
components[name] = {
"value": float(value),
"scale": float(scale),
"weight": float(weight),
"configuredWeight": float(configured_weight),
"freeThreshold": float(free_threshold),
"excessAboveFreeThreshold": float(excess),
"normalized": float(normalized),
"cost": float(contribution),
"screenInputSuppressed": bool(is_screen and not score_screen_residual),
}
total_cost += contribution
@ -1031,6 +1110,17 @@ def candidate_dynamics_cost(
),
"components": components,
"screenSpaceIncluded": screen_space_metrics is not None,
"screenSpaceInputKind": (
screen_input_kind if screen_space_metrics is not None else None
),
"screenSpaceSoftCostEnabled": bool(
screen_space_metrics is not None and score_screen_residual
),
"screenSpaceInputContract": (
"Score only stabilized semantic-focus residual UV: "
"projected_focus_uv - stabilized_desired_uv. Raw subject UV is "
"diagnostic-only and is excluded from soft candidate ranking."
),
"policy": asdict(selected_policy),
}
return float(total_cost), details
@ -1042,8 +1132,15 @@ def evaluate_candidate_dynamics(
*,
sample_rate: float = 60.0,
policy: CandidateDynamicsPolicy | None = None,
screen_input_kind: str = RAW_SUBJECT_SCREEN_INPUT,
) -> dict[str, object]:
"""Measure and score a candidate in one call."""
"""Measure and score a candidate in one call.
``screen_positions_uv`` defaults to ``raw_subject_uv`` and is then kept
only for diagnostics and downstream hard-gate auditing. A caller that
wants screen-space soft scoring must pass a stabilized semantic-focus
residual and set ``screen_input_kind="stabilized_residual_uv"``.
"""
translation = translation_dynamics_metrics(
positions_meters,
@ -1063,6 +1160,7 @@ def evaluate_candidate_dynamics(
translation,
screen,
policy=policy,
screen_input_kind=screen_input_kind,
)
return {
**cost_details,
@ -1075,6 +1173,8 @@ __all__ = [
"C2_RESAMPLING_POLICY_VERSION",
"DYNAMICS_POLICY_VERSION",
"RADIAL_CLAMP_POLICY_VERSION",
"RAW_SUBJECT_SCREEN_INPUT",
"STABILIZED_SCREEN_RESIDUAL_INPUT",
"TRAJECTORY_QUALITY_VERSION",
"CandidateDynamicsPolicy",
"ResamplingGuardPolicy",

View File

@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -0,0 +1,129 @@
Metadata-Version: 2.1
Name: importlib_metadata
Version: 8.0.0
Summary: Read metadata from Python packages
Author-email: "Jason R. Coombs" <jaraco@jaraco.com>
Project-URL: Source, https://github.com/python/importlib_metadata
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Requires-Python: >=3.8
Description-Content-Type: text/x-rst
License-File: LICENSE
Requires-Dist: zipp >=0.5
Requires-Dist: typing-extensions >=3.6.4 ; python_version < "3.8"
Provides-Extra: doc
Requires-Dist: sphinx >=3.5 ; extra == 'doc'
Requires-Dist: jaraco.packaging >=9.3 ; extra == 'doc'
Requires-Dist: rst.linker >=1.9 ; extra == 'doc'
Requires-Dist: furo ; extra == 'doc'
Requires-Dist: sphinx-lint ; extra == 'doc'
Requires-Dist: jaraco.tidelift >=1.4 ; extra == 'doc'
Provides-Extra: perf
Requires-Dist: ipython ; extra == 'perf'
Provides-Extra: test
Requires-Dist: pytest !=8.1.*,>=6 ; extra == 'test'
Requires-Dist: pytest-checkdocs >=2.4 ; extra == 'test'
Requires-Dist: pytest-cov ; extra == 'test'
Requires-Dist: pytest-mypy ; extra == 'test'
Requires-Dist: pytest-enabler >=2.2 ; extra == 'test'
Requires-Dist: pytest-ruff >=0.2.1 ; extra == 'test'
Requires-Dist: packaging ; extra == 'test'
Requires-Dist: pyfakefs ; extra == 'test'
Requires-Dist: flufl.flake8 ; extra == 'test'
Requires-Dist: pytest-perf >=0.9.2 ; extra == 'test'
Requires-Dist: jaraco.test >=5.4 ; extra == 'test'
Requires-Dist: importlib-resources >=1.3 ; (python_version < "3.9") and extra == 'test'
.. image:: https://img.shields.io/pypi/v/importlib_metadata.svg
:target: https://pypi.org/project/importlib_metadata
.. image:: https://img.shields.io/pypi/pyversions/importlib_metadata.svg
.. image:: https://github.com/python/importlib_metadata/actions/workflows/main.yml/badge.svg
:target: https://github.com/python/importlib_metadata/actions?query=workflow%3A%22tests%22
:alt: tests
.. image:: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/charliermarsh/ruff/main/assets/badge/v2.json
:target: https://github.com/astral-sh/ruff
:alt: Ruff
.. image:: https://readthedocs.org/projects/importlib-metadata/badge/?version=latest
:target: https://importlib-metadata.readthedocs.io/en/latest/?badge=latest
.. image:: https://img.shields.io/badge/skeleton-2024-informational
:target: https://blog.jaraco.com/skeleton
.. image:: https://tidelift.com/badges/package/pypi/importlib-metadata
:target: https://tidelift.com/subscription/pkg/pypi-importlib-metadata?utm_source=pypi-importlib-metadata&utm_medium=readme
Library to access the metadata for a Python package.
This package supplies third-party access to the functionality of
`importlib.metadata <https://docs.python.org/3/library/importlib.metadata.html>`_
including improvements added to subsequent Python versions.
Compatibility
=============
New features are introduced in this third-party library and later merged
into CPython. The following table indicates which versions of this library
were contributed to different versions in the standard library:
.. list-table::
:header-rows: 1
* - importlib_metadata
- stdlib
* - 7.0
- 3.13
* - 6.5
- 3.12
* - 4.13
- 3.11
* - 4.6
- 3.10
* - 1.4
- 3.8
Usage
=====
See the `online documentation <https://importlib-metadata.readthedocs.io/>`_
for usage details.
`Finder authors
<https://docs.python.org/3/reference/import.html#finders-and-loaders>`_ can
also add support for custom package installers. See the above documentation
for details.
Caveats
=======
This project primarily supports third-party packages installed by PyPA
tools (or other conforming packages). It does not support:
- Packages in the stdlib.
- Packages installed without metadata.
Project details
===============
* Project home: https://github.com/python/importlib_metadata
* Report bugs at: https://github.com/python/importlib_metadata/issues
* Code hosting: https://github.com/python/importlib_metadata
* Documentation: https://importlib-metadata.readthedocs.io/
For Enterprise
==============
Available as part of the Tidelift Subscription.
This project and the maintainers of thousands of other packages are working with Tidelift to deliver one enterprise subscription that covers all of the open source you use.
`Learn more <https://tidelift.com/subscription/pkg/pypi-importlib-metadata?utm_source=pypi-importlib-metadata&utm_medium=referral&utm_campaign=github>`_.

View File

@ -0,0 +1,32 @@
importlib_metadata-8.0.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
importlib_metadata-8.0.0.dist-info/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
importlib_metadata-8.0.0.dist-info/METADATA,sha256=anuQ7_7h4J1bSEzfcjIBakPi2cyVQ7y7jklLHsBeH1k,4648
importlib_metadata-8.0.0.dist-info/RECORD,,
importlib_metadata-8.0.0.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
importlib_metadata-8.0.0.dist-info/WHEEL,sha256=mguMlWGMX-VHnMpKOjjQidIo1ssRlCFu4a4mBpz1s2M,91
importlib_metadata-8.0.0.dist-info/top_level.txt,sha256=CO3fD9yylANiXkrMo4qHLV_mqXL2sC5JFKgt1yWAT-A,19
importlib_metadata/__init__.py,sha256=tZNB-23h8Bixi9uCrQqj9Yf0aeC--Josdy3IZRIQeB0,33798
importlib_metadata/__pycache__/__init__.cpython-312.pyc,,
importlib_metadata/__pycache__/_adapters.cpython-312.pyc,,
importlib_metadata/__pycache__/_collections.cpython-312.pyc,,
importlib_metadata/__pycache__/_compat.cpython-312.pyc,,
importlib_metadata/__pycache__/_functools.cpython-312.pyc,,
importlib_metadata/__pycache__/_itertools.cpython-312.pyc,,
importlib_metadata/__pycache__/_meta.cpython-312.pyc,,
importlib_metadata/__pycache__/_text.cpython-312.pyc,,
importlib_metadata/__pycache__/diagnose.cpython-312.pyc,,
importlib_metadata/_adapters.py,sha256=rIhWTwBvYA1bV7i-5FfVX38qEXDTXFeS5cb5xJtP3ks,2317
importlib_metadata/_collections.py,sha256=CJ0OTCHIjWA0ZIVS4voORAsn2R4R2cQBEtPsZEJpASY,743
importlib_metadata/_compat.py,sha256=73QKrN9KNoaZzhbX5yPCCZa-FaALwXe8TPlDR72JgBU,1314
importlib_metadata/_functools.py,sha256=PsY2-4rrKX4RVeRC1oGp1lB1pmC9eKN88_f-bD9uOoA,2895
importlib_metadata/_itertools.py,sha256=cvr_2v8BRbxcIl5x5ldfqdHjhI8Yi8s8yk50G_nm6jQ,2068
importlib_metadata/_meta.py,sha256=nxZ7C8GVlcBFAKWyVOn_dn7ot_twBcbm1NmvjIetBHI,1801
importlib_metadata/_text.py,sha256=HCsFksZpJLeTP3NEk_ngrAeXVRRtTrtyh9eOABoRP4A,2166
importlib_metadata/compat/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
importlib_metadata/compat/__pycache__/__init__.cpython-312.pyc,,
importlib_metadata/compat/__pycache__/py311.cpython-312.pyc,,
importlib_metadata/compat/__pycache__/py39.cpython-312.pyc,,
importlib_metadata/compat/py311.py,sha256=uqm-K-uohyj1042TH4a9Er_I5o7667DvulcD-gC_fSA,608
importlib_metadata/compat/py39.py,sha256=cPkMv6-0ilK-0Jw_Tkn0xYbOKJZc4WJKQHow0c2T44w,1102
importlib_metadata/diagnose.py,sha256=nkSRMiowlmkhLYhKhvCg9glmt_11Cox-EmLzEbqYTa8,379
importlib_metadata/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0

View File

@ -0,0 +1,5 @@
Wheel-Version: 1.0
Generator: setuptools (70.1.1)
Root-Is-Purelib: true
Tag: py3-none-any

Binary file not shown.

View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2012 Daniel Holth <dholth@fastmail.fm> and contributors
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

View File

@ -0,0 +1,66 @@
Metadata-Version: 2.3
Name: wheel
Version: 0.45.1
Summary: A built-package format for Python
Keywords: wheel,packaging
Author-email: Daniel Holth <dholth@fastmail.fm>
Maintainer-email: Alex Grönholm <alex.gronholm@nextday.fi>
Requires-Python: >=3.8
Description-Content-Type: text/x-rst
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: Topic :: System :: Archiving :: Packaging
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Requires-Dist: pytest >= 6.0.0 ; extra == "test"
Requires-Dist: setuptools >= 65 ; extra == "test"
Project-URL: Changelog, https://wheel.readthedocs.io/en/stable/news.html
Project-URL: Documentation, https://wheel.readthedocs.io/
Project-URL: Issue Tracker, https://github.com/pypa/wheel/issues
Project-URL: Source, https://github.com/pypa/wheel
Provides-Extra: test
wheel
=====
This is a command line tool for manipulating Python wheel files, as defined in
`PEP 427`_. It contains the following functionality:
* Convert ``.egg`` archives into ``.whl``
* Unpack wheel archives
* Repack wheel archives
* Add or remove tags in existing wheel archives
.. _PEP 427: https://www.python.org/dev/peps/pep-0427/
Historical note
---------------
This project used to contain the implementation of the setuptools_ ``bdist_wheel``
command, but as of setuptools v70.1, it no longer needs ``wheel`` installed for that to
work. Thus, you should install this **only** if you intend to use the ``wheel`` command
line tool!
.. _setuptools: https://pypi.org/project/setuptools/
Documentation
-------------
The documentation_ can be found on Read The Docs.
.. _documentation: https://wheel.readthedocs.io/
Code of Conduct
---------------
Everyone interacting in the wheel project's codebases, issue trackers, chat
rooms, and mailing lists is expected to follow the `PSF Code of Conduct`_.
.. _PSF Code of Conduct: https://github.com/pypa/.github/blob/main/CODE_OF_CONDUCT.md

View File

@ -0,0 +1,68 @@
../../Scripts/wheel.exe,sha256=A69vg7y22iGuDMaqOWKEJzU_3jcjugdke2okLg6B5Cg,108448
wheel-0.45.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
wheel-0.45.1.dist-info/LICENSE.txt,sha256=MMI2GGeRCPPo6h0qZYx8pBe9_IkcmO8aifpP8MmChlQ,1107
wheel-0.45.1.dist-info/METADATA,sha256=mKz84H7m7jsxJyzeIcTVORiTb0NPMV39KvOIYhGgmjA,2313
wheel-0.45.1.dist-info/RECORD,,
wheel-0.45.1.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
wheel-0.45.1.dist-info/WHEEL,sha256=CpUCUxeHQbRN5UGRQHYRJorO5Af-Qy_fHMctcQ8DSGI,82
wheel-0.45.1.dist-info/entry_points.txt,sha256=rTY1BbkPHhkGMm4Q3F0pIzJBzW2kMxoG1oriffvGdA0,104
wheel/__init__.py,sha256=mrxMnvdXACur_LWegbUfh5g5ysWZrd63UJn890wvGNk,59
wheel/__main__.py,sha256=NkMUnuTCGcOkgY0IBLgBCVC_BGGcWORx2K8jYGS12UE,455
wheel/__pycache__/__init__.cpython-312.pyc,,
wheel/__pycache__/__main__.cpython-312.pyc,,
wheel/__pycache__/_bdist_wheel.cpython-312.pyc,,
wheel/__pycache__/_setuptools_logging.cpython-312.pyc,,
wheel/__pycache__/bdist_wheel.cpython-312.pyc,,
wheel/__pycache__/macosx_libfile.cpython-312.pyc,,
wheel/__pycache__/metadata.cpython-312.pyc,,
wheel/__pycache__/util.cpython-312.pyc,,
wheel/__pycache__/wheelfile.cpython-312.pyc,,
wheel/_bdist_wheel.py,sha256=UghCQjSH_pVfcZh6oRjzSw_TQhcf3anSx1OkiLSL82M,21694
wheel/_setuptools_logging.py,sha256=-5KC-lne0ilOUWIDfOkqapUWGMFZhuKYDIavIZiB5kM,781
wheel/bdist_wheel.py,sha256=tpf9WufiSO1RuEMg5oPhIfSG8DMziCZ_4muCKF69Cqo,1107
wheel/cli/__init__.py,sha256=Npq6_jKi03dhIcRnmbuFhwviVJxwO0tYEnEhWMv9cJo,4402
wheel/cli/__pycache__/__init__.cpython-312.pyc,,
wheel/cli/__pycache__/convert.cpython-312.pyc,,
wheel/cli/__pycache__/pack.cpython-312.pyc,,
wheel/cli/__pycache__/tags.cpython-312.pyc,,
wheel/cli/__pycache__/unpack.cpython-312.pyc,,
wheel/cli/convert.py,sha256=Bi0ntEXb9nTllCxWeTRQ4j-nPs3szWSEKipG_GgnMkQ,12634
wheel/cli/pack.py,sha256=CAFcHdBVulvsHYJlndKVO7KMI9JqBTZz5ii0PKxxCOs,3103
wheel/cli/tags.py,sha256=lHw-LaWrkS5Jy_qWcw-6pSjeNM6yAjDnqKI3E5JTTCU,4760
wheel/cli/unpack.py,sha256=Y_J7ynxPSoFFTT7H0fMgbBlVErwyDGcObgme5MBuz58,1021
wheel/macosx_libfile.py,sha256=k1x7CE3LPtOVGqj6NXQ1nTGYVPaeRrhVzUG_KPq3zDs,16572
wheel/metadata.py,sha256=JC4p7jlQZu2bUTAQ2fevkqLjg_X6gnNyRhLn6OUO1tc,6171
wheel/util.py,sha256=aL7aibHwYUgfc8WlolL5tXdkV4DatbJxZHb1kwHFJAU,423
wheel/vendored/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
wheel/vendored/__pycache__/__init__.cpython-312.pyc,,
wheel/vendored/packaging/LICENSE,sha256=ytHvW9NA1z4HS6YU0m996spceUDD2MNIUuZcSQlobEg,197
wheel/vendored/packaging/LICENSE.APACHE,sha256=DVQuDIgE45qn836wDaWnYhSdxoLXgpRRKH4RuTjpRZQ,10174
wheel/vendored/packaging/LICENSE.BSD,sha256=tw5-m3QvHMb5SLNMFqo5_-zpQZY2S8iP8NIYDwAo-sU,1344
wheel/vendored/packaging/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
wheel/vendored/packaging/__pycache__/__init__.cpython-312.pyc,,
wheel/vendored/packaging/__pycache__/_elffile.cpython-312.pyc,,
wheel/vendored/packaging/__pycache__/_manylinux.cpython-312.pyc,,
wheel/vendored/packaging/__pycache__/_musllinux.cpython-312.pyc,,
wheel/vendored/packaging/__pycache__/_parser.cpython-312.pyc,,
wheel/vendored/packaging/__pycache__/_structures.cpython-312.pyc,,
wheel/vendored/packaging/__pycache__/_tokenizer.cpython-312.pyc,,
wheel/vendored/packaging/__pycache__/markers.cpython-312.pyc,,
wheel/vendored/packaging/__pycache__/requirements.cpython-312.pyc,,
wheel/vendored/packaging/__pycache__/specifiers.cpython-312.pyc,,
wheel/vendored/packaging/__pycache__/tags.cpython-312.pyc,,
wheel/vendored/packaging/__pycache__/utils.cpython-312.pyc,,
wheel/vendored/packaging/__pycache__/version.cpython-312.pyc,,
wheel/vendored/packaging/_elffile.py,sha256=hbmK8OD6Z7fY6hwinHEUcD1by7czkGiNYu7ShnFEk2k,3266
wheel/vendored/packaging/_manylinux.py,sha256=P7sdR5_7XBY09LVYYPhHmydMJIIwPXWsh4olk74Uuj4,9588
wheel/vendored/packaging/_musllinux.py,sha256=z1s8To2hQ0vpn_d-O2i5qxGwEK8WmGlLt3d_26V7NeY,2674
wheel/vendored/packaging/_parser.py,sha256=4tT4emSl2qTaU7VTQE1Xa9o1jMPCsBezsYBxyNMUN-s,10347
wheel/vendored/packaging/_structures.py,sha256=q3eVNmbWJGG_S0Dit_S3Ao8qQqz_5PYTXFAKBZe5yr4,1431
wheel/vendored/packaging/_tokenizer.py,sha256=alCtbwXhOFAmFGZ6BQ-wCTSFoRAJ2z-ysIf7__MTJ_k,5292
wheel/vendored/packaging/markers.py,sha256=_TSPI1BhJYO7Bp9AzTmHQxIqHEVXaTjmDh9G-w8qzPA,8232
wheel/vendored/packaging/requirements.py,sha256=dgoBeVprPu2YE6Q8nGfwOPTjATHbRa_ZGLyXhFEln6Q,2933
wheel/vendored/packaging/specifiers.py,sha256=IWSt0SrLSP72heWhAC8UL0eGvas7XIQHjqiViVfmPKE,39778
wheel/vendored/packaging/tags.py,sha256=fedHXiOHkBxNZTXotXv8uXPmMFU9ae-TKBujgYHigcA,18950
wheel/vendored/packaging/utils.py,sha256=XgdmP3yx9-wQEFjO7OvMj9RjEf5JlR5HFFR69v7SQ9E,5268
wheel/vendored/packaging/version.py,sha256=PFJaYZDxBgyxkfYhH3SQw4qfE9ICCWrTmitvq14y3bs,16234
wheel/vendored/vendor.txt,sha256=Z2ENjB1i5prfez8CdM1Sdr3c6Zxv2rRRolMpLmBncAE,16
wheel/wheelfile.py,sha256=USCttNlJwafxt51YYFFKG7jnxz8dfhbyqAZL6jMTA9s,8411

View File

@ -0,0 +1,4 @@
Wheel-Version: 1.0
Generator: flit 3.10.1
Root-Is-Purelib: true
Tag: py3-none-any

View File

@ -0,0 +1,6 @@
[console_scripts]
wheel=wheel.cli:main
[distutils.commands]
bdist_wheel=wheel.bdist_wheel:bdist_wheel

View File

@ -0,0 +1,78 @@
{
"schemaVersion": "cw-camera-worker-distribution-build-v1",
"createdUtc": "2026-08-08T20:03:52.1316389Z",
"workerVersion": "0.1.5",
"protocolVersion": "1",
"buildIdentitySchemaVersion": "cw-camera-worker-build-identity-v1",
"buildEnvironment": {
"kind": "disposable_venv",
"pythonVersion": "3.12.13",
"isolatedPrefixVerified": true,
"userSiteEnabled": false,
"threadLimits": {
"OMP_NUM_THREADS": "1",
"OPENBLAS_NUM_THREADS": "1",
"MKL_NUM_THREADS": "1",
"NUMEXPR_NUM_THREADS": "1",
"VECLIB_MAXIMUM_THREADS": "1",
"BLIS_NUM_THREADS": "1"
},
"installedDistributions": [
"altgraph==0.17.5",
"audioread==3.1.0",
"certifi==2024.12.14",
"cffi==2.0.0",
"charset-normalizer==3.4.1",
"decorator==5.1.1",
"idna==3.10",
"joblib==1.5.2",
"lazy_loader==0.4",
"librosa==0.11.0",
"llvmlite==0.43.0",
"msgpack==1.1.2",
"numba==0.60.0",
"numpy==1.26.4",
"packaging==26.2",
"pefile==2024.8.26",
"pip==25.0.1",
"platformdirs==4.9.6",
"pooch==1.8.2",
"pycparser==2.21",
"pyinstaller==6.17.0",
"pyinstaller-hooks-contrib==2025.10",
"pywin32-ctypes==0.2.3",
"requests==2.32.3",
"scikit-learn==1.7.2",
"scipy==1.15.3",
"setuptools==78.1.0",
"soundfile==0.13.1",
"soxr==1.0.0",
"threadpoolctl==3.6.0",
"typing_extensions==4.15.0",
"urllib3==2.3.0",
"wheel==0.45.1"
]
},
"requirements": [
{
"path": "MachineLearning/CameraDirector/requirements-worker.txt",
"sha256": "90987782184e15119a6090922e464559e620b6b164b80b5ae4f567e8ae6a7b9d"
},
{
"path": "MachineLearning/CameraDirector/requirements-worker-build.txt",
"sha256": "1ae29e0b81e82bdcbcb14d1817813e5f60e45ae260af0e693a66cc24f3ae6827"
}
],
"payload": {
"fileCount": 646,
"bytes": 267603627,
"executableSha256": "055fb16b60454b0d131ed2201eccad8d11977bd99ef011468d44638b33e4a1ef",
"buildIdentitySha256": "84428e90a523ec40aa54d45c7b795e586979fce6f4a0e139e0bd83fc3c0ce906",
"sanityRange": {
"minimumFileCount": 500,
"maximumFileCount": 1000,
"minimumBytes": 209715200,
"maximumBytes": 393216000
}
}
}

View File

@ -1,6 +1,6 @@
{
"name": "com.mingle.cw-ai",
"version": "0.4.8",
"version": "0.4.9",
"displayName": "Mingle Camera Work AI",
"description": "Self-contained high-quality Unity Timeline camera generation with an embedded prepared reference library, per-shot editable clips, and A/B review tools.",
"unity": "6000.0",

View File

@ -37,16 +37,16 @@ Install **Git and Git LFS before opening Unity**, then use **Add package from
git URL** with:
```text
https://kindnick-git.duckdns.org/mingle/streamingle-unity-utilities.git?path=/CameraAI~#v0.1.6
https://kindnick-git.duckdns.org/mingle/streamingle-unity-utilities.git?path=/CameraAI~#v0.1.15
```
The Camera AI package includes the complete Windows x64
`CWCameraWorker.exe` onedir build, so artist workstations do not need Python.
The executable must remain beside its `_internal` directory. Authored camera,
character-motion, and source-audio reference data are not mirrored in this
utility repository; connect an access-controlled `CW-AI` data root containing
`DatasetExports`, `reports/training_index.json`, and
`models/cut_ranker_v2.json`.
`CWCameraWorker.exe` onedir build and a read-only prepared reference library,
so artist workstations do not need Python or a separate `CW-AI` checkout.
The executable must remain beside its `_internal` directory. A newer
access-controlled `CW-AI` data root containing `DatasetExports`,
`reports/training_index.json`, and `models/cut_ranker_v2.json` remains an
optional override.
After installation, open `Tools > Streamingle > AI 카메라 생성`. The package
automatically locates its bundled worker and validates the configured data