Files
rangerhq-buddy/inc/state.php
T
ranger f661eabba1 tune: lower wink probability from 30% to 5%
The original 30% per-render rate felt disturbing in practice. Default
mood is 80, so most admin/dashboard renders cleared the >=75 gate, and
~30% of those flipped to wink. Stacked across the main admin page +
dashboard widget visible on the same screen, the visible wink rate
felt closer to "stuck" than "playful" — buddy looked like he had one
eye closed all the time instead of occasionally cheeking out.

5% is true Easter-egg territory: rare enough to feel magical when it
lands, frequent enough you'll catch it after a few admin sessions.
Refresh ~20 times before expecting to see one.

CHANGES
- inc/state.php: mt_rand probability gate 30 -> 5
- inc/state.php: docstring updated to match new ~5% probability

NOT CHANGED
- Wink visuals (sprite.php) unchanged — still the same closed-eye arc,
  asymmetric smirk, rosier cheeks. Just rarer.
- Version constant + CHANGELOG entry left at v0.1.1 — this is a tuning
  hotfix, not new behavior worth a version bump. If we want a v0.1.2
  bump for hygiene, that's a separate commit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 07:16:32 +01:00

124 lines
4.6 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
/**
* Buddy state — persistence for the pet's stats and identity.
*
* Storage: per-user, in `wp_usermeta` under key `buddy_state`. A single
* JSON-shaped associative array. Picked user_meta over site-wide
* options so multiple admins on the same site each get their own pet,
* matching the per-user mental model.
*
* Stats range 0100. Higher = better. Decay happens via WP-cron later
* (Phase C); for Phase A the stats just persist as last set.
*/
if ( ! defined( 'ABSPATH' ) ) { exit; }
const BUDDY_META_KEY = 'buddy_state';
/**
* Default state for a freshly-adopted pet. Used the first time a user
* visits a Buddy-rendering page.
*/
function buddy_default_state() {
return array(
'name' => __( 'Buddy', 'buddy' ),
'species' => 'default',
'hunger' => 80,
'happiness' => 80,
'health' => 90,
'energy' => 70,
'born_at' => time(),
'last_tick' => time(),
);
}
/**
* Fetch the current user's Buddy state, falling back to defaults and
* persisting them on first read so the pet has a "birthday" timestamp.
*
* @param int $user_id Optional. Defaults to current user.
* @return array { name, species, hunger, happiness, health, energy, born_at, last_tick }
*/
function buddy_get_state( $user_id = 0 ) {
$user_id = $user_id ? (int) $user_id : get_current_user_id();
if ( ! $user_id ) { return buddy_default_state(); }
$state = get_user_meta( $user_id, BUDDY_META_KEY, true );
if ( ! is_array( $state ) || empty( $state ) ) {
$state = buddy_default_state();
update_user_meta( $user_id, BUDDY_META_KEY, $state );
}
// Merge against defaults so newly-added keys appear with sensible
// values for users adopted under earlier versions.
return array_merge( buddy_default_state(), $state );
}
/**
* Persist a partial state update for the current user. Unknown keys
* are silently dropped; values are clamped to valid ranges.
*
* @param array $patch Keys: name (str), species (str), hunger/happiness/health/energy (int 0100).
* @return array The full updated state.
*/
function buddy_update_state( array $patch, $user_id = 0 ) {
$user_id = $user_id ? (int) $user_id : get_current_user_id();
if ( ! $user_id ) { return buddy_default_state(); }
$state = buddy_get_state( $user_id );
$stat_keys = array( 'hunger', 'happiness', 'health', 'energy' );
foreach ( $stat_keys as $k ) {
if ( array_key_exists( $k, $patch ) ) {
$state[ $k ] = max( 0, min( 100, (int) $patch[ $k ] ) );
}
}
if ( array_key_exists( 'name', $patch ) ) {
$name = sanitize_text_field( (string) $patch['name'] );
if ( $name !== '' && mb_strlen( $name ) <= 32 ) {
$state['name'] = $name;
}
}
if ( array_key_exists( 'species', $patch ) ) {
$allowed_species = array( 'default' ); // Phase D will widen this.
$sp = sanitize_key( (string) $patch['species'] );
if ( in_array( $sp, $allowed_species, true ) ) {
$state['species'] = $sp;
}
}
update_user_meta( $user_id, BUDDY_META_KEY, $state );
return $state;
}
/**
* Average of the four stats — used as a single "mood" indicator for
* the dashboard widget. Returns 0100.
*/
function buddy_overall_mood( array $state ) {
$sum = (int) $state['hunger'] + (int) $state['happiness'] + (int) $state['health'] + (int) $state['energy'];
return (int) round( $sum / 4 );
}
/**
* Pick a one-word status label + sprite tone based on overall mood.
* Pure cosmetic, used in the dashboard widget header and the main
* admin page.
*
* Easter-egg: when Buddy is genuinely happy (mood >= 75) there's a
* ~5% chance per page-render of returning the "Cheeky" wink tone
* instead of the standard happy face. Rare enough to feel magical
* rather than disturbing — refresh the page a few dozen times and
* you'll catch it.
*/
function buddy_mood_label( $mood_score ) {
if ( $mood_score >= 75 && mt_rand( 1, 100 ) <= 5 ) {
return array( 'label' => __( 'Cheeky 😉', 'buddy' ), 'tone' => 'wink' );
}
if ( $mood_score >= 80 ) { return array( 'label' => __( 'Thriving', 'buddy' ), 'tone' => 'happy' ); }
if ( $mood_score >= 60 ) { return array( 'label' => __( 'Content', 'buddy' ), 'tone' => 'happy' ); }
if ( $mood_score >= 40 ) { return array( 'label' => __( 'Okay', 'buddy' ), 'tone' => 'neutral' ); }
if ( $mood_score >= 20 ) { return array( 'label' => __( 'Hungry', 'buddy' ), 'tone' => 'sad' ); }
return array( 'label' => __( 'Distressed', 'buddy' ), 'tone' => 'sad' );
}