Skip to content

Latest commit

 

History

History
490 lines (359 loc) · 14 KB

File metadata and controls

490 lines (359 loc) · 14 KB

Integrating BreakEscape into Hacktivity

Prerequisites

  • Hacktivity running Rails 7.0+
  • PostgreSQL database
  • User model with Devise
  • Pundit for authorization (recommended)

Installation Steps

1. Add to Gemfile

# Gemfile (in Hacktivity repository)
gem 'break_escape', path: '../BreakEscape'

2. Install and Migrate

bundle install
rails break_escape:install:migrations
rails db:migrate
rails db:seed  # Creates missions from scenario directories

3. Mount Engine

# config/routes.rb
mount BreakEscape::Engine => "/break_escape"

4. Configure

# config/initializers/break_escape.rb
BreakEscape.configure do |config|
  config.standalone_mode = false  # Mounted mode in Hacktivity
end

5. Verify User Model

Ensure your User model has these methods for Pundit authorization:

class User < ApplicationRecord
  def admin?
    # Your admin check logic
  end

  def account_manager?
    # Optional: account manager check logic
  end
end

6. Add Navigation Link (Optional)

<!-- In your Hacktivity navigation -->
<%= link_to "BreakEscape", break_escape_path %>

7. Restart Server

rails restart
# or
touch tmp/restart.txt

8. Verify Installation

Navigate to: https://your-hacktivity.com/break_escape/

You should see the mission selection screen.

Configuration Options

Environment Variables

# .env (or similar)
BREAK_ESCAPE_STANDALONE=false  # Mounted mode (default)

Custom Configuration

# config/initializers/break_escape.rb
BreakEscape.configure do |config|
  # Mode
  config.standalone_mode = false

  # Demo user (only used in standalone mode)
  config.demo_user_handle = ENV['BREAK_ESCAPE_DEMO_USER'] || 'demo_player'
end

Authorization Integration

BreakEscape uses Pundit policies by default. It expects:

Game Access

  • Owner: Users can only access their own games
  • Admin/Account Manager: Can access all games

Mission Visibility

  • All Users: Can see published missions
  • Admin/Account Manager: Can see all missions (including unpublished)

Custom Policies

To customize authorization, create policy overrides in Hacktivity:

# app/policies/break_escape/game_policy.rb (in Hacktivity)
module BreakEscape
  class GamePolicy < ::BreakEscape::GamePolicy
    def show?
      # Custom logic here
      super || custom_access_check?
    end
  end
end

Database Tables

BreakEscape adds 3 tables to your database:

  1. break_escape_missions - Metadata for scenarios

    • name, display_name, description, published, difficulty_level
  2. break_escape_games - Player game instances

    • player (polymorphic: User), mission_id, scenario_data (JSONB), player_state (JSONB)
  3. break_escape_demo_users - Optional (standalone mode only)

    • Only created if migrations run, can be safely ignored in mounted mode

API Endpoints

Once mounted, these endpoints are available:

  • Mission List: GET /break_escape/missions
  • Play Mission: GET /break_escape/missions/:id
  • Game View: GET /break_escape/games/:id
  • Scenario Data: GET /break_escape/games/:id/scenario
  • NPC Scripts: GET /break_escape/games/:id/ink?npc=:npc_id
  • Bootstrap: GET /break_escape/games/:id/bootstrap
  • State Sync: PUT /break_escape/games/:id/sync_state
  • Unlock: POST /break_escape/games/:id/unlock
  • Inventory: POST /break_escape/games/:id/inventory

Asset Serving

Static game assets are served from public/break_escape/:

  • JavaScript: public/break_escape/js/
  • CSS: public/break_escape/css/
  • Images: public/break_escape/assets/

These are served by the engine's static file middleware.

Troubleshooting

404 errors on /break_escape/

Solution: Ensure engine is mounted in config/routes.rb

mount BreakEscape::Engine => "/break_escape"

Authentication errors

Solution: Verify current_user method works in your ApplicationController

# In Hacktivity's ApplicationController
def current_user
  # Should return User instance or nil
end

Asset 404s (CSS/JS not loading)

Solution: Check that public/break_escape/ directory exists and contains game files

ls public/break_escape/js/
ls public/break_escape/css/
ls public/break_escape/assets/

Ink compilation errors

Solution: Verify bin/inklecate executable exists and is executable

chmod +x scenarios/inklecate
# Or ensure inklecate is in PATH

CSRF token errors on API calls

Solution: Ensure your layout includes CSRF meta tags

<!-- In application.html.erb -->
<%= csrf_meta_tags %>

Database migration issues

Solution: Check PostgreSQL is running and migrations ran successfully

rails db:migrate:status | grep break_escape
# Should show all migrations as "up"

Game screen is blank / Phaser never starts

Symptom: Browser console shows Refused to load the script 'https://cdn.jsdelivr.net/...' or Refused to execute inline script.

Solution: Hacktivity's CSP is blocking BreakEscape's scripts. Follow the Content Security Policy (CSP) Configuration section above and add the required sources. The most common causes:

  • cdn.jsdelivr.net, unpkg.com, or ajax.googleapis.com missing from script-src → Phaser, EasyStar.js, Tippy.js, and the WebFont Loader all fail silently
  • content_security_policy_nonce_directives does not include style-src → inline <style nonce="..."> blocks on games/new and missions/index are blocked
  • Nonce generator not configured → every <script nonce="..."> tag in BreakEscape views renders with an empty nonce and is refused

Open the browser DevTools → Console. Each CSP violation names the blocked URL or "inline script" / "inline style" and the directive that rejected it — use that to pinpoint which source or directive is missing.

Game fonts missing (Press Start 2P / VT323 render as fallback)

Symptom: Pixel/retro fonts don't appear; text uses a generic sans-serif.

Solution: Add Google Fonts to the CSP:

policy.style_src *policy.style_src, "https://fonts.googleapis.com"
policy.font_src  *policy.font_src,  "https://fonts.gstatic.com", :data

CyberChef workstation iframe is blank

Symptom: Clicking the Crypto Workstation opens the panel but it stays empty.

Solution: Add frame and worker sources:

policy.frame_src  *policy.frame_src,  :self
policy.worker_src *policy.worker_src, :self, "blob:"

blob: is required for CyberChef's Tesseract OCR and Forge prime web workers.

TTS Voice Cache

BreakEscape pre-generates NPC dialogue audio using the Gemini TTS API and commits the resulting MP3 files to the engine repository. This means no Gemini API key or quota is needed at runtime — audio is served straight from disk.

Cache Location

The cache lives at tts_cache/ inside the engine repository root:

BreakEscape/
  tts_cache/
    m01_first_contact/    ← per-scenario subdirectory
      <md5hash>.mp3       ← one file per unique dialogue line
    ceo_exfil/
      ...

The TtsService constant is:

CACHE_DIR = BreakEscape::Engine.root.join("tts_cache")

Engine.root always resolves to the engine gem directory, so the cache path is identical in both standalone mode and when the engine is mounted into Hacktivity via path: in the Gemfile.

Serving Audio

Audio is served through the authenticated POST /games/:id/tts controller action, which validates that the requested text matches the NPC's actual Ink dialogue before returning the cached MP3. Static-file fallback is not used — all TTS requests go through the controller so authentication and text validation cannot be bypassed.

Pre-generating New Audio

When scenario dialogue changes or a new scenario is added, regenerate the cache with the batch rake task:

# From the BreakEscape engine directory
bundle exec rake app:break_escape:tts:batch_generate[scenario_name]
# e.g.
bundle exec rake app:break_escape:tts:batch_generate[m01_first_contact]

Set GEMINI_API_KEY before running. The batch processor:

  • skips lines already cached (cache-hit fast path)
  • skips phone NPCs (npcType: "phone") — these use client-side text chat
  • applies exponential back-off on quota errors

Commit the resulting tts_cache/<scenario>/ files to git so that Hacktivity deployments pick them up automatically.

Cleaning Up Stale Cache Files

A helper script identifies and removes cache files that should no longer exist (e.g. audio generated for phone-NPC Ink dialogue before the batch processor was updated to skip them):

# Preview what would be deleted
ruby scripts/tts_cache_cleanup_phone.rb

# Actually delete
ruby scripts/tts_cache_cleanup_phone.rb --delete

Performance Considerations

JIT Ink Compilation

  • First NPC interaction compiles .ink.json (~300ms)
  • Subsequent interactions use cached JSON (~10ms)
  • Compiled files persist across restarts
  • Production: Pre-compile all .ink files during deployment

Scenario Generation

  • ERB templates render on game creation (~50ms)
  • Scenario data cached in games.scenario_data JSONB
  • No re-rendering during gameplay

State Sync

  • Periodic sync every 30 seconds (configurable)
  • Uses Rails cache for temporary state
  • Database writes only on unlock/inventory changes

Content Security Policy (CSP) Configuration

BreakEscape loads external libraries and uses inline scripts with nonces. When mounting the engine into Hacktivity you must extend the host CSP to allow the sources below, otherwise scripts, fonts, and the CyberChef iframe will be blocked.

Add or extend a content_security_policy initializer in Hacktivity:

# config/initializers/content_security_policy.rb  (in Hacktivity)
Rails.application.configure do
  config.content_security_policy do |policy|
    # --- BreakEscape external script sources ---
    # Phaser 3 + EasyStar.js
    # Tippy.js + Popper.js
    # WebFont Loader
    policy.script_src *policy.script_src,
      "https://cdn.jsdelivr.net",
      "https://unpkg.com",
      "https://ajax.googleapis.com"

    # --- BreakEscape font sources ---
    # Google Fonts stylesheet (loads as a <link>, but style-src covers @import)
    policy.style_src *policy.style_src,
      "https://fonts.googleapis.com"

    # Google Fonts binary files + data URIs used by some icon sets
    policy.font_src *policy.font_src,
      "https://fonts.gstatic.com",
      :data

    # --- CyberChef iframe ---
    # CyberChef is served from the engine's own /break_escape/assets/cyberchef/
    # path, so 'self' is sufficient.  The iframe has its own browsing context;
    # it does NOT inherit the parent page's nonce, so its internal scripts are
    # governed by the iframe's own CSP (or lack thereof for same-origin content).
    policy.frame_src *policy.frame_src, :self

    # --- CyberChef Web Workers (Tesseract OCR, Forge prime worker) ---
    # These run inside the CyberChef iframe context, so worker-src must also
    # allow 'self' and blob: (workers are often created via blob URLs).
    policy.worker_src *policy.worker_src, :self, "blob:"

    # --- Nonce directives ---
    # Ensure nonces are generated for both scripts and styles so that the
    # engine's inline <script nonce="..."> and <style nonce="..."> tags work.
  end

  # Generate a fresh nonce per request and apply it to both script-src and
  # style-src (BreakEscape uses nonces on inline <style> blocks too).
  config.content_security_policy_nonce_generator = ->(request) { SecureRandom.base64(16) }
  config.content_security_policy_nonce_directives = %w[script-src style-src]
end

Note: The *policy.script_src spread syntax preserves whatever Hacktivity already has in that directive (e.g. 'self', 'nonce-...') and appends only the new sources. If Hacktivity's policy is built incrementally you may need to adjust the syntax to match its pattern.

Why each source is needed

Source Directive Used by
cdn.jsdelivr.net script-src Phaser 3.60, EasyStar.js 0.4.4
unpkg.com script-src Tippy.js 6, Popper.js 2
ajax.googleapis.com script-src WebFont Loader 1.6
fonts.googleapis.com style-src Google Fonts CSS
fonts.gstatic.com font-src Google Fonts binary files
data: font-src Icon data URIs in CSS
'self' frame-src CyberChef iframe (/break_escape/assets/cyberchef/)
'self' + blob: worker-src CyberChef's Tesseract OCR and Forge prime workers

Security Notes

  1. CSRF Protection: All POST/PUT endpoints require valid CSRF tokens
  2. Authorization: Pundit policies enforce access control
  3. XSS Prevention: All inline scripts and styles use CSP nonces; eval() is not used; inline event handlers (onclick, onerror) are not used — see CSP section above for required host configuration
  4. SQL Injection: All queries use parameterized statements
  5. Session Security: Sessions tied to user authentication

Monitoring

Key Metrics to Track

  • Game session duration
  • Mission completion rates
  • Unlock attempt failures (may indicate difficulty issues)
  • Ink compilation times (should be ~300ms first time)
  • State sync success rate

Logs to Monitor

# Game creation
"[BreakEscape] Game created: ID=123, Mission=ceo_exfil"

# Ink compilation
"[BreakEscape] Compiling helper1_greeting.ink..."
"[BreakEscape] Compiled helper1_greeting.ink (45.2 KB)"

# Unlock validation
"[BreakEscape] Unlock validated: door=office, method=password"

Updating BreakEscape

cd ../BreakEscape
git pull origin main

cd ../Hacktivity
bundle install
rails break_escape:install:migrations  # Install new migrations
rails db:migrate
rails restart

Support

For issues specific to BreakEscape engine:

  • Check README.md in BreakEscape repository
  • Review implementation plan in planning_notes/
  • Check game client logs in browser console

For Hacktivity integration issues:

  • Verify Devise authentication is working
  • Check Pundit policies are configured
  • Review Rails logs for errors