feat(city): add bump-to-talk conversations with persistent context

Implements proximity-based street conversations between player characters and peers (human or agentic NPC). New PersonTalk model, controller, and migrations store daily message threads that persist across sessions and provide context for future interactions. Agent replies include backoff throttling and fallback persona handling to avoid dead-end chats.

Frontend changes refactor the chat panel to support both character and agent peers, opening automatically when the player moves within range, polling for replies, rendering day separators, and displaying rate-limit warnings. Styling updated to a transparent blurred panel. Translations extended with new hint text and rate-limit messages across en, es, and pt_BR.
This commit is contained in:
2026-10-31 17:59:22 -03:00
parent 50a6d40e8e
commit 59963de995
13 changed files with 1475 additions and 57 deletions
@@ -0,0 +1,46 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
/**
* Creates the "person_talks" table.
*
* Daily-registered street conversations between a player character and
* either another character (human peer) or an agentic NPC (AiAgent).
* Rows are the persistent context used to keep the thread of previous
* days when the pair talks again.
*/
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('person_talks', function (Blueprint $table): void {
$table->id();
$table->foreignId('from_character_id')->nullable()->constrained('characters')->cascadeOnDelete();
$table->foreignId('from_agent_id')->nullable()->constrained('ai_agents')->cascadeOnDelete();
$table->foreignId('to_character_id')->nullable()->constrained('characters')->cascadeOnDelete();
$table->foreignId('to_agent_id')->nullable()->constrained('ai_agents')->cascadeOnDelete();
$table->string('body', 1000);
$table->date('talked_on');
$table->timestamps();
$table->index(['talked_on']);
$table->index(['from_character_id', 'to_character_id']);
$table->index(['from_character_id', 'to_agent_id']);
$table->index(['from_agent_id', 'to_character_id']);
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('person_talks');
}
};
@@ -0,0 +1,35 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
/**
* Adds the "is_fallback" flag to "person_talks".
*
* Error-fallback rows keep the chat from hanging, but they must never be
* fed back to the model as part of the conversation (an apology would
* pollute the persona's context and skew future replies).
*/
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('person_talks', function (Blueprint $table): void {
$table->boolean('is_fallback')->default(false);
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('person_talks', function (Blueprint $table): void {
$table->dropColumn('is_fallback');
});
}
};