@@ -52,6 +52,15 @@ use ladybug::core::simd::{self, hamming_distance};
5252use ladybug:: nars:: TruthValue ;
5353use ladybug:: storage:: service:: { CognitiveService , CpuFeatures , ServiceConfig } ;
5454use ladybug:: storage:: { Addr , BindSpace , CogRedis , FINGERPRINT_WORDS , RedisResult } ;
55+ use ladybug:: container:: Container ;
56+ use ladybug:: qualia:: texture:: { GraphMetrics , compute as compute_texture} ;
57+ use ladybug:: qualia:: agent_state:: { AgentState , PresenceMode , SelfDimensions } ;
58+ use ladybug:: qualia:: felt_parse:: { GhostEcho , GhostType } ;
59+ use ladybug:: qualia:: felt_traversal:: FeltPath ;
60+ use ladybug:: qualia:: reflection:: { ReflectionResult , ReflectionEntry , ReflectionOutcome } ;
61+ use ladybug:: qualia:: volition:: { VolitionalAgenda , CouncilWeights } ;
62+ use ladybug_contract:: nars:: TruthValue as ContractTruthValue ;
63+ use ladybug:: cognitive:: RungLevel ;
5564use ladybug:: { FINGERPRINT_BITS , FINGERPRINT_BYTES , VERSION } ;
5665
5766// =============================================================================
@@ -644,6 +653,9 @@ fn route(
644653 ( "POST" , "/api/v1/graph/hydrate" ) => handle_graph_hydrate ( body, state, format) ,
645654 ( "POST" , "/api/v1/graph/search" ) => handle_graph_search ( body, state, format) ,
646655
656+ // Qualia hydration — full QualiaSnapshot from address/fingerprint
657+ ( "POST" , "/api/v1/hydrate" ) => handle_qualia_hydrate ( body, state, format) ,
658+
647659 // LanceDB-compatible API
648660 ( "POST" , "/api/v1/lance/table" ) => handle_lance_create_table ( body, format) ,
649661 ( "POST" , "/api/v1/lance/add" ) => handle_lance_add ( body, state, format) ,
@@ -2275,6 +2287,345 @@ fn handle_graph_search(body: &str, state: &SharedState, format: ResponseFormat)
22752287 }
22762288}
22772289
2290+ // =============================================================================
2291+ // QUALIA HYDRATION HANDLER
2292+ // =============================================================================
2293+
2294+ /// POST /api/v1/hydrate
2295+ ///
2296+ /// Full qualia hydration: address/fingerprint → QualiaSnapshot (JSON).
2297+ ///
2298+ /// Input (JSON):
2299+ /// ```json
2300+ /// {
2301+ /// "address": "0x8000", // hex address in BindSpace (preferred)
2302+ /// "fingerprint": "<base64>", // OR raw base64 fingerprint
2303+ /// "presence": "wife", // optional: wife|work|agi|hybrid|neutral
2304+ /// "surprise": 0.5, // optional: felt surprise [0,1]
2305+ /// "confidence": 0.6, // optional: NARS confidence [0,1]
2306+ /// "rung": 3, // optional: cognitive rung 0-9
2307+ /// "ghosts": [ // optional: active ghost echoes
2308+ /// {"type": "love", "intensity": 0.7},
2309+ /// {"type": "staunen", "intensity": 0.4}
2310+ /// ]
2311+ /// }
2312+ /// ```
2313+ ///
2314+ /// Output: Full QualiaSnapshot with texture, felt physics, core axes,
2315+ /// moment awareness, mode, hints, and qualia preamble text.
2316+ fn handle_qualia_hydrate ( body : & str , state : & SharedState , format : ResponseFormat ) -> Vec < u8 > {
2317+ // ── 1. Resolve fingerprint → Container ──────────────────────────────
2318+ let container: Container = if let Some ( addr_raw) = extract_json_hex_u16 ( body, "address" ) {
2319+ let db = state. read ( ) . unwrap ( ) ;
2320+ let bs = db. cog_redis . bind_space ( ) ;
2321+ let addr = Addr ( addr_raw) ;
2322+ if let Some ( node) = bs. read ( addr) {
2323+ Container :: from ( & Fingerprint :: from_raw ( {
2324+ let mut w = [ 0u64 ; ladybug:: FINGERPRINT_U64 ] ;
2325+ for ( i, & word) in node. fingerprint . iter ( ) . enumerate ( ) {
2326+ if i < w. len ( ) {
2327+ w[ i] = word;
2328+ }
2329+ }
2330+ w
2331+ } ) )
2332+ } else {
2333+ return http_error ( 404 , "not_found" , "Address not occupied in BindSpace" , format) ;
2334+ }
2335+ } else if let Some ( fp_b64) = extract_json_str ( body, "fingerprint" ) {
2336+ let bytes = match base64_decode ( & fp_b64) {
2337+ Ok ( b) => b,
2338+ Err ( _) => return http_error ( 400 , "bad_request" , "Invalid base64 fingerprint" , format) ,
2339+ } ;
2340+ let mut words = [ 0u64 ; ladybug:: container:: CONTAINER_WORDS ] ;
2341+ for ( i, chunk) in bytes. chunks ( 8 ) . enumerate ( ) {
2342+ if i >= words. len ( ) {
2343+ break ;
2344+ }
2345+ let mut buf = [ 0u8 ; 8 ] ;
2346+ buf[ ..chunk. len ( ) ] . copy_from_slice ( chunk) ;
2347+ words[ i] = u64:: from_le_bytes ( buf) ;
2348+ }
2349+ let mut c = Container :: zero ( ) ;
2350+ c. words . copy_from_slice ( & words) ;
2351+ c
2352+ } else {
2353+ return http_error ( 400 , "missing_field" , "Need 'address' or 'fingerprint'" , format) ;
2354+ } ;
2355+
2356+ // ── 2. Compute 8D texture ───────────────────────────────────────────
2357+ let metrics = GraphMetrics :: default ( ) ;
2358+ let texture = compute_texture ( & container, & metrics) ;
2359+
2360+ // ── 3. Parse optional context ───────────────────────────────────────
2361+ let presence = extract_json_str ( body, "presence" )
2362+ . map ( |s| PresenceMode :: from_str ( & s) )
2363+ . unwrap_or_default ( ) ;
2364+
2365+ let surprise = extract_json_f32 ( body, "surprise" ) . unwrap_or ( texture. entropy ) ;
2366+ let confidence = extract_json_f32 ( body, "confidence" ) . unwrap_or ( 0.5 ) ;
2367+
2368+ let rung_idx = extract_json_usize ( body, "rung" ) . unwrap_or ( 0 ) as u8 ;
2369+ let rung = match rung_idx {
2370+ 0 => RungLevel :: Surface ,
2371+ 1 => RungLevel :: Shallow ,
2372+ 2 => RungLevel :: Contextual ,
2373+ 3 => RungLevel :: Analogical ,
2374+ 4 => RungLevel :: Abstract ,
2375+ 5 => RungLevel :: Structural ,
2376+ 6 => RungLevel :: Counterfactual ,
2377+ 7 => RungLevel :: Meta ,
2378+ 8 => RungLevel :: Recursive ,
2379+ 9 => RungLevel :: Transcendent ,
2380+ _ => RungLevel :: Surface ,
2381+ } ;
2382+
2383+ // Parse ghosts from JSON array (lightweight manual parse)
2384+ let ghosts = parse_ghost_array ( body) ;
2385+
2386+ // ── 4. Build lightweight FeltPath + Reflection for AgentState ────────
2387+ let felt_path = FeltPath {
2388+ choices : vec ! [ ] ,
2389+ target : ladybug:: container:: adjacency:: PackedDn :: new ( & [ 0 ] ) ,
2390+ total_surprise : surprise,
2391+ mean_surprise : surprise,
2392+ path_context : container. clone ( ) ,
2393+ } ;
2394+
2395+ let reflection = ReflectionResult {
2396+ entries : vec ! [ ReflectionEntry {
2397+ dn: ladybug:: container:: adjacency:: PackedDn :: new( & [ 0 ] ) ,
2398+ outcome: if surprise > 0.6 {
2399+ ReflectionOutcome :: Explore
2400+ } else {
2401+ ReflectionOutcome :: Stable
2402+ } ,
2403+ surprise,
2404+ truth_before: ContractTruthValue :: new( 0.5 , confidence) ,
2405+ truth_after: ContractTruthValue :: new( 0.5 , confidence) ,
2406+ depth: 1 ,
2407+ } ] ,
2408+ felt_path : felt_path. clone ( ) ,
2409+ hydration_candidates : vec ! [ ] ,
2410+ } ;
2411+
2412+ let agenda = VolitionalAgenda {
2413+ acts : vec ! [ ] ,
2414+ reflection : reflection. clone ( ) ,
2415+ chains : vec ! [ ] ,
2416+ total_energy : surprise,
2417+ decisiveness : 0.5 ,
2418+ } ;
2419+
2420+ let council = CouncilWeights {
2421+ guardian_surprise_factor : 1.0 ,
2422+ catalyst_surprise_factor : 1.0 ,
2423+ balanced_factor : 1.0 ,
2424+ } ;
2425+
2426+ // ── 5. Compute full AgentState ──────────────────────────────────────
2427+ let agent_state = AgentState :: compute (
2428+ & texture,
2429+ & felt_path,
2430+ & reflection,
2431+ & agenda,
2432+ ghosts. clone ( ) ,
2433+ rung,
2434+ council,
2435+ presence,
2436+ SelfDimensions :: default ( ) ,
2437+ ) ;
2438+
2439+ let hints = agent_state. to_hints ( ) ;
2440+ let preamble = agent_state. qualia_preamble ( ) ;
2441+
2442+ // ── 6. Serialize response ───────────────────────────────────────────
2443+ match format {
2444+ ResponseFormat :: Arrow => {
2445+ // Arrow: return texture 8 floats + derived signals
2446+ let schema = Arc :: new ( Schema :: new ( vec ! [
2447+ Field :: new( "entropy" , DataType :: Float32 , false ) ,
2448+ Field :: new( "purity" , DataType :: Float32 , false ) ,
2449+ Field :: new( "density" , DataType :: Float32 , false ) ,
2450+ Field :: new( "bridgeness" , DataType :: Float32 , false ) ,
2451+ Field :: new( "warmth" , DataType :: Float32 , false ) ,
2452+ Field :: new( "edge" , DataType :: Float32 , false ) ,
2453+ Field :: new( "depth" , DataType :: Float32 , false ) ,
2454+ Field :: new( "flow" , DataType :: Float32 , false ) ,
2455+ Field :: new( "staunen" , DataType :: Float32 , false ) ,
2456+ Field :: new( "wisdom" , DataType :: Float32 , false ) ,
2457+ Field :: new( "ache" , DataType :: Float32 , false ) ,
2458+ Field :: new( "presence_val" , DataType :: Float32 , false ) ,
2459+ Field :: new( "tension" , DataType :: Float32 , false ) ,
2460+ Field :: new( "mode" , DataType :: Utf8 , false ) ,
2461+ Field :: new( "rung" , DataType :: Utf8 , false ) ,
2462+ Field :: new( "preamble" , DataType :: Utf8 , false ) ,
2463+ ] ) ) ;
2464+ let batch = RecordBatch :: try_new (
2465+ schema,
2466+ vec ! [
2467+ Arc :: new( Float32Array :: from( vec![ texture. entropy] ) ) as ArrayRef ,
2468+ Arc :: new( Float32Array :: from( vec![ texture. purity] ) ) as ArrayRef ,
2469+ Arc :: new( Float32Array :: from( vec![ texture. density] ) ) as ArrayRef ,
2470+ Arc :: new( Float32Array :: from( vec![ texture. bridgeness] ) ) as ArrayRef ,
2471+ Arc :: new( Float32Array :: from( vec![ texture. warmth] ) ) as ArrayRef ,
2472+ Arc :: new( Float32Array :: from( vec![ texture. edge] ) ) as ArrayRef ,
2473+ Arc :: new( Float32Array :: from( vec![ texture. depth] ) ) as ArrayRef ,
2474+ Arc :: new( Float32Array :: from( vec![ texture. flow] ) ) as ArrayRef ,
2475+ Arc :: new( Float32Array :: from( vec![ agent_state. felt. staunen] ) ) as ArrayRef ,
2476+ Arc :: new( Float32Array :: from( vec![ agent_state. felt. wisdom] ) ) as ArrayRef ,
2477+ Arc :: new( Float32Array :: from( vec![ agent_state. felt. ache] ) ) as ArrayRef ,
2478+ Arc :: new( Float32Array :: from( vec![ agent_state. moment. presence] ) ) as ArrayRef ,
2479+ Arc :: new( Float32Array :: from( vec![ agent_state. moment. tension] ) ) as ArrayRef ,
2480+ Arc :: new( StringArray :: from( vec![ format!( "{:?}" , agent_state. mode) ] ) ) as ArrayRef ,
2481+ Arc :: new( StringArray :: from( vec![ format!( "{:?}" , agent_state. rung) ] ) ) as ArrayRef ,
2482+ Arc :: new( StringArray :: from( vec![ preamble. clone( ) ] ) ) as ArrayRef ,
2483+ ] ,
2484+ )
2485+ . unwrap ( ) ;
2486+ http_arrow ( 200 , & batch)
2487+ }
2488+ ResponseFormat :: Json => {
2489+ // Build ghost JSON array
2490+ let ghost_json: Vec < String > = ghosts
2491+ . iter ( )
2492+ . map ( |g| {
2493+ format ! (
2494+ r#"{{"type":"{:?}","intensity":{:.4}}}"# ,
2495+ g. ghost_type, g. intensity
2496+ )
2497+ } )
2498+ . collect ( ) ;
2499+
2500+ // Build hints JSON
2501+ let hints_json: Vec < String > = hints
2502+ . iter ( )
2503+ . map ( |( k, v) | format ! ( r#""{}": {:.4}"# , k, v) )
2504+ . collect ( ) ;
2505+
2506+ let json_body = format ! (
2507+ concat!(
2508+ r#"{{"texture":{{"entropy":{:.4},"purity":{:.4},"density":{:.4},"# ,
2509+ r#""bridgeness":{:.4},"warmth":{:.4},"edge":{:.4},"depth":{:.4},"flow":{:.4}}},"# ,
2510+ r#""felt":{{"staunen":{:.4},"wisdom":{:.4},"ache":{:.4},"libido":{:.4},"lingering":{:.4}}},"# ,
2511+ r#""core":{{"alpha":{:.4},"gamma":{:.4},"omega":{:.4},"phi":{:.4}}},"# ,
2512+ r#""moment":{{"now_density":{:.4},"tension":{:.4},"katharsis":{},"presence":{:.4}}},"# ,
2513+ r#""presence_mode":"{:?}","mode":"{:?}","rung":"{:?}","# ,
2514+ r#""ghosts":[{}],"hints":{{{}}},"preamble":{}}}"# ,
2515+ ) ,
2516+ texture. entropy,
2517+ texture. purity,
2518+ texture. density,
2519+ texture. bridgeness,
2520+ texture. warmth,
2521+ texture. edge,
2522+ texture. depth,
2523+ texture. flow,
2524+ agent_state. felt. staunen,
2525+ agent_state. felt. wisdom,
2526+ agent_state. felt. ache,
2527+ agent_state. felt. libido,
2528+ agent_state. felt. lingering,
2529+ agent_state. core. alpha,
2530+ agent_state. core. gamma,
2531+ agent_state. core. omega,
2532+ agent_state. core. phi,
2533+ agent_state. moment. now_density,
2534+ agent_state. moment. tension,
2535+ agent_state. moment. katharsis,
2536+ agent_state. moment. presence,
2537+ agent_state. presence_mode,
2538+ agent_state. mode,
2539+ agent_state. rung,
2540+ ghost_json. join( "," ) ,
2541+ hints_json. join( "," ) ,
2542+ json_escape_string( & preamble) ,
2543+ ) ;
2544+
2545+ http_json ( 200 , & json_body)
2546+ }
2547+ }
2548+ }
2549+
2550+ /// Parse ghost array from JSON body.
2551+ /// Expects: "ghosts": [{"type":"love","intensity":0.7}, ...]
2552+ fn parse_ghost_array ( body : & str ) -> Vec < GhostEcho > {
2553+ let mut ghosts = Vec :: new ( ) ;
2554+ let pattern = r#""ghosts":["# ;
2555+ let start = match body. find ( pattern) {
2556+ Some ( s) => s + pattern. len ( ) ,
2557+ None => return ghosts,
2558+ } ;
2559+ let rest = & body[ start..] ;
2560+ let end = match rest. find ( ']' ) {
2561+ Some ( e) => e,
2562+ None => return ghosts,
2563+ } ;
2564+ let inner = & rest[ ..end] ;
2565+
2566+ // Split by "},{" to get individual ghost objects
2567+ for chunk in inner. split ( '}' ) {
2568+ let chunk = chunk. trim ( ) . trim_start_matches ( ',' ) . trim ( ) ;
2569+ if chunk. is_empty ( ) {
2570+ continue ;
2571+ }
2572+ // Extract type
2573+ let ghost_type = if let Some ( idx) = chunk. find ( r#""type":"# ) {
2574+ let rest = & chunk[ idx + 7 ..] ;
2575+ let rest = rest. trim ( ) . trim_start_matches ( '"' ) ;
2576+ let end = rest. find ( '"' ) . unwrap_or ( rest. len ( ) ) ;
2577+ match rest[ ..end] . to_lowercase ( ) . as_str ( ) {
2578+ "love" => GhostType :: Love ,
2579+ "epiphany" => GhostType :: Epiphany ,
2580+ "arousal" => GhostType :: Arousal ,
2581+ "staunen" | "wonder" | "awe" => GhostType :: Staunen ,
2582+ "wisdom" => GhostType :: Wisdom ,
2583+ "thought" => GhostType :: Thought ,
2584+ "grief" => GhostType :: Grief ,
2585+ "boundary" => GhostType :: Boundary ,
2586+ _ => GhostType :: Staunen ,
2587+ }
2588+ } else {
2589+ continue ;
2590+ } ;
2591+
2592+ // Extract intensity
2593+ let intensity = if let Some ( idx) = chunk. find ( r#""intensity":"# ) {
2594+ let rest = & chunk[ idx + 12 ..] ;
2595+ let end = rest
2596+ . find ( |c : char | !c. is_ascii_digit ( ) && c != '.' && c != '-' )
2597+ . unwrap_or ( rest. len ( ) ) ;
2598+ rest[ ..end] . parse :: < f32 > ( ) . unwrap_or ( 0.5 )
2599+ } else {
2600+ 0.5
2601+ } ;
2602+
2603+ ghosts. push ( GhostEcho {
2604+ ghost_type,
2605+ intensity,
2606+ } ) ;
2607+ }
2608+ ghosts
2609+ }
2610+
2611+ /// Escape a string for JSON output.
2612+ fn json_escape_string ( s : & str ) -> String {
2613+ let mut out = String :: with_capacity ( s. len ( ) + 2 ) ;
2614+ out. push ( '"' ) ;
2615+ for c in s. chars ( ) {
2616+ match c {
2617+ '"' => out. push_str ( "\\ \" " ) ,
2618+ '\\' => out. push_str ( "\\ \\ " ) ,
2619+ '\n' => out. push_str ( "\\ n" ) ,
2620+ '\r' => out. push_str ( "\\ r" ) ,
2621+ '\t' => out. push_str ( "\\ t" ) ,
2622+ c => out. push ( c) ,
2623+ }
2624+ }
2625+ out. push ( '"' ) ;
2626+ out
2627+ }
2628+
22782629// =============================================================================
22792630// UDP BITPACKED HAMMING HANDLER
22802631// =============================================================================
0 commit comments