Add special case: Incineration

This commit is contained in:
Ladd Hoffman 2023-03-13 22:40:44 -05:00
parent 63b43a0f4d
commit 5988950857
6 changed files with 113 additions and 17 deletions

View File

@ -3,7 +3,7 @@ import { Action } from '../display/action.js';
import { Actor } from '../display/actor.js';
import params from '../../params.js';
import { ReputationHolder } from '../reputation/reputation-holder.js';
import { displayNumber, EPSILON } from '../../util.js';
import { displayNumber, EPSILON, INCINERATOR_ADDRESS } from '../../util.js';
const CITATION = 'citation';
const BALANCE = 'balance';
@ -70,9 +70,14 @@ export class Forum extends ReputationHolder {
}
async addPost(authorId, postContent) {
console.log('addPost', { authorId, postContent });
const post = new Post(this, authorId, postContent);
this.posts.addVertex(post.id, post, post.getLabel());
for (const { postId: citedPostId, weight } of post.citations) {
// Special case: Incinerator
if (citedPostId === INCINERATOR_ADDRESS && !this.posts.getVertex(INCINERATOR_ADDRESS)) {
this.posts.addVertex(INCINERATOR_ADDRESS, { name: 'Incinerator' }, 'Incinerator');
}
this.posts.addEdge(CITATION, post.id, citedPostId, weight);
}
return post;
@ -171,22 +176,43 @@ export class Forum extends ReputationHolder {
for (const citationEdge of citationEdges) {
const { weight } = citationEdge;
let outboundAmount = weight * increment;
if (Math.abs(outboundAmount) > EPSILON) {
const balanceToOutbound = this.posts.getEdgeWeight(BALANCE, citationEdge.from, citationEdge.to) ?? 0;
let refundFromOutbound = 0;
// Special case: Incineration.
if (citationEdge.to.id === INCINERATOR_ADDRESS) {
// Only a positive amount may be incinerated! Otherwise the sink could be used as a source.
if (outboundAmount < 0) {
throw new Error('Incinerator can only receive positive citations!');
}
// Reputation sent to the incinerator is burned! This means it is deducted from the sender,
// without increasing the value of any other token.
this.actions.propagate.log(citationEdge.from.data, { name: 'Incinerator' }, `(${increment})`);
} else {
// We need to ensure that we at most undo the prior effects of this post
if (initialNegative) {
outboundAmount = outboundAmount < 0
? Math.max(outboundAmount, -balanceToOutbound)
: Math.min(outboundAmount, -balanceToOutbound);
}
if (Math.abs(outboundAmount) > EPSILON) {
const refundFromOutbound = await this.propagateValue(citationEdge, {
// Recursively propagate reputation effects
refundFromOutbound = await this.propagateValue(citationEdge, {
rewardsAccumulator,
increment: outboundAmount,
depth: depth + 1,
initialNegative: initialNegative || (depth === 0 && outboundAmount < 0),
});
// Any excess (negative) amount that could not be propagated,
// i.e. because a cited post has been reduced to zero value,
// is retained by the citing post.
outboundAmount -= refundFromOutbound;
}
// Keep a record of the effect of the reputation transferred along this edge in the graph,
// so that later, negative citations can be constrained to at most undo these effects.
this.posts.setEdgeWeight(BALANCE, citationEdge.from, citationEdge.to, balanceToOutbound + outboundAmount);
totalOutboundAmount += outboundAmount;

View File

@ -22,6 +22,7 @@
<li><a href="./tests/forum5.test.html">Destroy a post after it has received positive citations</a></li>
<li><a href="./tests/forum6.test.html">Initially zero-valued posts later receive citations</a></li>
<li><a href="./tests/forum7.test.html">Negatively cite a zero-valued post</a></li>
<li><a href="./tests/forum8.test.html">Incinerate reputation</a></li>
</ol>
</ul>
<ul>

View File

@ -34,6 +34,7 @@
<script type="module" src="./scripts/forum/forum5.test.js"></script>
<script type="module" src="./scripts/forum/forum6.test.js"></script>
<script type="module" src="./scripts/forum/forum7.test.js"></script>
<script type="module" src="./scripts/forum/forum8.test.js"></script>
<script defer class="mocha-init">
mocha.setup({
ui: 'bdd',

View File

@ -0,0 +1,26 @@
<!DOCTYPE html>
<head>
<title>Forum test 8</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="https://unpkg.com/mocha/mocha.css" />
<link type="text/css" rel="stylesheet" href="../index.css" />
</head>
<body>
<h2><a href="../">DGF Tests</a></h2>
<div id="mocha"></div>
<div id="scene"></div>
</body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/radash/10.7.0/radash.js"
integrity="sha512-S207zKWG3iqXqe6msO7/Mr8X3DzzF4u8meFlokHjGtBPTGUhgzVo0lpcqEy0GoiMUdcoct+H+SqzoLsxXbynzg=="
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://unpkg.com/mocha/mocha.js"></script>
<script src="https://unpkg.com/chai/chai.js"></script>
<script type="module" src="./scripts/forum/forum8.test.js"></script>
<script defer class="mocha-init">
mocha.setup({
ui: 'bdd',
});
chai.should();
</script>

View File

@ -0,0 +1,40 @@
import { INCINERATOR_ADDRESS, mochaRun } from '../../../util.js';
import { ForumTest } from './forum.test-util.js';
describe('Forum', function tests() {
this.timeout(0);
const forumTest = new ForumTest();
before(async () => {
await forumTest.setup();
});
context('Incinerate reputation', async () => {
let forum;
let experts;
let posts;
before(() => {
forum = forumTest.forum;
experts = forumTest.experts;
posts = forumTest.posts;
});
it('Post1', async () => {
await forumTest.addPost(experts[0], 10);
forum.getPost(posts[0]).value.should.equal(10);
});
it('Post2 burns reputation from Post1', async () => {
await forumTest.addPost(experts[0], 10, [
{ postId: posts[0], weight: -1 },
{ postId: INCINERATOR_ADDRESS, weight: 1 },
]);
forum.getPost(posts[0]).value.should.equal(0);
forum.getPost(posts[1]).value.should.equal(0);
});
});
});
mochaRun();

View File

@ -4,6 +4,8 @@ const timers = new Map();
export const EPSILON = 2.23e-16;
export const INCINERATOR_ADDRESS = 0;
export const debounce = async (fn, delayMs) => {
const timer = timers.get(fn);
if (timer) {